I would test presenter like this:
class MostPopularPresenter #Inject constructor(val mostPopularUseCase: MostPopularUseCase)
: Presenter<MostPopularView>() {
fun requestMostPopular(page: Int, update: Boolean) {
if (page <= 6)
mostPopularUseCase.execute(MostPopularObserver(), MostPopularUseCase.Params.createQuery(page, 15, update))
}
inner class MostPopularObserver : DisposableSingleObserver<MostPopularModel>() {
override fun onSuccess(t: MostPopularModel) {
this#MostPopularPresenter.view?.populateRecyclerList(t)
}
override fun onError(e: Throwable) {
this#MostPopularPresenter.view?.showError()
}
}
}
I have problem how to mock observer and force it to throw error or return value on success. I'm using mockito/junit. Can someone point me how to achieve it? Maybe my code is untestable?
An observer is an object that shouldn't be really tested. It has been already tested when it has been developed by a third developer, although there are some people that say, with a part of reason, that you should also test a third party library in order to ensure that it doesn't break your code between versions.
So, if you don't test the observer... how do you test your code? Simply, what you really need to test is the presenter itself. The code running inside the observer is part of the presenter. So instead of mocking the observer mock the useCase:
test useCaseFails() {
val usecase = // mock use case
when(usecase.execute(...))
.thenAnswer(/* receive the observer as first parameter
and make it emit an error */)
val presenter = ...
presenter.requestMostPopular(...)
// assert that presenter.view?.showError has been called
}
Another way of doing this (at least this is the way I usually code) is to make the useCase return an observable and subscribe it in the presenter:
class MostPopularPresenter #Inject constructor(val mostPopularUseCase: MostPopularUseCase)
: Presenter<MostPopularView>() {
private var lateinit observer : Disposable
fun requestMostPopular(page: Int, update: Boolean) {
if (page <= 6)
disposable = mostPopularUseCase.execute(MostPopularUseCase.Params.createQuery(page, 15, update))
.subscribe(t -> view?.populateRecyclerList(t),
e -> view?.showError())
}
}
This way you can easily mock your useCase so it returns a Subject you can control:
test useCaseFails() {
val usecase = // mock use case
val subject = PublishSubject()
when(usecase.execute(...))
.thenReturn(subject)
val presenter = ...
presenter.requestMostPopular(...)
subject.emitError(...) // <- pseudocode
// assert that presenter.view?.showError has been called
}
Usually there are not many cases where it is absolutely not possible to test. As far as I see it, you have a few options:
Put the observer into the constructor with a default value (but this might have some downsides with your dependency injection)
Put the observer into the function with a default value. This would work, but you have to choose if your API should contain this
Use the observer as property. In the test you can override this one.
All this variants would work and are listed here:
// observer in constructor
class MostPopularPresenter #Inject constructor(val mostPopularUseCase: MostPopularUseCase, val observer: DisposableSingleObserver<MostPopularModel> = MostPopularObserver())
: Presenter<MostPopularView>() {
// observer as property
internal var observer: DisposableSingleObserver<MostPopularModel> = MostPopularObserver()
// observer in function
fun requestMostPopular(page: Int, update: Boolean, observer: DisposableSingleObserver<MostPopularModel> = MostPopularObserver()) {
if (page <= 6)
mostPopularUseCase.execute(observer, MostPopularUseCase.Params.createQuery(page, 15, update))
}
}
internal class MostPopularObserver : DisposableSingleObserver<MostPopularModel>() { ... }
It would be even nicer, if you us a DisposableSingleObserverFactory and create the observer when it's needed.
class MostPopularPresenter #Inject constructor(val mostPopularUseCase: MostPopularUseCase, val observerFactory: DisposableSingleObserverFactory<MostPopularModel> = MostPopularObserverFactorty())
: Presenter<MostPopularView>() {
internal var observerFactory: DisposableSingleObserverFactory<MostPopularModel> = MostPopularObserverFactory()
fun requestMostPopular(page: Int, update: Boolean, observerFactory: DisposableSingleObserverFactory<MostPopularModel> = MostPopularObserver()) {
if (page <= 6)
mostPopularUseCase.execute(observerFactory.create(), MostPopularUseCase.Params.createQuery(page, 15, update))
}
}
internal class MostPopularObserver : DisposableSingleObserver<MostPopularModel>() {
Related
I'm trying to use Mockk to mock a method with context receiver:
class MyClass {
// The method I'm going to mock
context(CallContext)
fun myMethod(a: Int) Int { a }
}
It's hard to get the instance of CallContext in the unit test. So I hope I can write a unit test in this way:
/*
This should work, but I can't get the CallContext instance
with(callContextInstance) {
Every { mockedMyClass.myMethod(1) } returns 2
}
*/
// I hope a unit test can be written like this... But it won't compile now.
with(any<CallContext>) {
Every { mockedMyClass.myMethod(1) } returns 2
}
So what should I do? Thanks in advance.
At the time of writing, MockK does not support context receivers, and it probably won't until context receivers are released - so some time after Kotlin 1.9, so maybe in 2024).
(Context receivers are explicitly described as not ready for production. A stable release won't be available until after the K2 release, and the K2 beta is targeted for Kotlin 1.9, which has a planned release of December 2023.)
That said, if anyone wants to attempt support, then get stuck in! MockK is an community supported open source project that accepts PRs.
Confounding factors
However, there are two hinderances before MockK can fully support context receivers:
Context receivers aren't finished, nor is their current implementation stable. KT-10468. Their implementation could change significantly. Trying to implement support for a moving target is challenging.
IDE support is limited, which makes developing with them difficult (follow KTIJ-20857 for updates)
Workaround
In the meantime, you could adjust your code to allow for manual mocking.
First, adjust MyClass to either be an open class, or introduce a new interface that describes the behaviour you want to mock (code to an interface).
/** Describe the API that [MyClass] will implement */
interface MyClassSpec {
context(CallContext)
fun myMethod(a: Int): Int
}
And then implement the interface
/** Concrete implementation of [MyClassSpec] */
class MyClass: MyClassSpec {
context(CallContext)
override fun myMethod(a: Int): Int = a
}
Now in your test you can create a mock by creating an anonymous object that implements MyClassSpec - and now you have a mock that supports context receivers.
#Test
fun myTest() {
val myClassMock = object : MyClassSpec {
context(CallContext)
override fun myMethod(a: Int): Int = 123
}
}
If I get the idea what exactly do you try to mock, the following works with mockk 1.13.3:
interface CallContext
class MyClass {
context(CallContext)
fun myMethod(a: Int): Int = a
}
class ContextMockTest {
private val myClassMock: MyClass = mockk()
#Test
fun mockContextWorks() {
every {
with(any<CallContext>()) {
myClassMock.myMethod(any())
}
} returns 123
val context = object : CallContext { }
with(context) {
assertEquals(123, myClassMock.myMethod(1))
}
verify {
with(any<CallContext>()) {
myClassMock.myMethod(1)
}
}
}
}
A link to the gist just in case
I have a problem with MockK.
I have a class:
#Service
class ItemServiceImpl(private val varPuObjectMapper: VarPuObjectMapper) : OutboundAdvicesService {
override suspend fun getItemsForWarehouse(warehouseId: String): ItemsDTO {
// do stuff
}
override suspend fun getPickingListsForWarehouse(warehouseId: String): PickingListsDTO {
val groupedOutboundAdvices = getItemsForWarehouse(warehouseId)
// do other stuff
}
}
and a test for this class:
class ItemServiceGroupingTest : FunSpec({
val warehouseId = "1"
val myObjectMapper = MyObjectMapper()
val itemService = mockk<ItemServiceImpl>()
beforeTest {
val items1 = myObjectMapper
.getObjectMapper()
.readValue(Mockups.ITEMS_1, ItemsDTO::class.java)
coEvery {
itemService.getItemsForWarehouse(warehouseId)
} returns items1
}
test("should get items for warehouse with ID 1") {
val itemsDTO = itemService.getItemsForWarehouse(warehouseId)
// assertions here
}
test("should get picking lists for warehouse with ID 1") {
val pickingLists = itemService.getPickingListsForWarehouse(warehouseId)
// assertions here
}
})
Now the first test passes successfully, but the second one fails:
no answer found for: ItemServiceImpl(#1).getPickingListsForWarehouse(1, continuation {})
io.mockk.MockKException: no answer found for: ItemServiceImpl(#1).getPickingListsForWarehouse(1, continuation {})
at app//io.mockk.impl.stub.MockKStub.defaultAnswer(MockKStub.kt:93)
From what I understand, this fails cause the getPickingListsForWarehouse method is not mocked. Is it possible to call a real method using MockK? I tried to use spyk instead of mockk, and I tried mockk with relaxed = true, but it got me nowhere...
The problem with the second test is that you are trying to call a method from a mock without specified behavior. The first test passes because you already set the value which should be returned for the method call itemService.getItemsForWarehouse(warehouseId) in this statement in beforeTest:
coEvery {
itemService.getItemsForWarehouse(warehouseId)
} returns items1
You have to do the same for getPickingListsForWarehouse or call a real method like:
every { itemService.getPickingListsForWarehouse(warehouseId) } answers { callOriginal() }
But then you have to use spyk instead of mock.
However, if you are asserting the object which you provided within the mock, you are not testing the real implementation of the method under test. You are just testing the mock, so if you change the implementation of your method this test still will be passing. beacuse it doesn't call your real object.
I'm having a hard time trying to get a private method in Kotlin using reflection in order to pass it as a parameter to a higher order function, here is what I got and what I need to do:
The function that gets the private method, probably what I should change or fix:
inline fun <reified T> T.getPrivateFunc(name: String): KFunction<*> {
return T::class.declaredMemberFunctions.first {
it.name == name
}.apply {
isAccessible = true
}
}
This is the high order function I have:
class MyService {
fun myHigherOrderFunction(action: () -> Unit) { /*...*/ }
}
These are the class and the private method I need to get somehow:
class SystemUnderTest {
fun privateFunc() { /*...*/ }
}
Finally a unit test where I I'm trying to make sure the proper method is passed to the high order function, I omitted details for simplification:
// ...
val serviceMock = MyService()
val sut = SystemUnderTest()
// Here is what I'm trying to accomplish
val privateMethod = sut.getPrivateMethod("privateFunc")
service.myHighOrderFunction(privateMethod)
// In the above line I get a compilation error: required () - Unit, found KFunction<*>
service.myHigherOrderFunction(privateMethod as () -> Unit)
// In the above line I get the following runtime error:
// ClassCastException: kotlin.reflect.jvm.internal.KFunctionImpl cannot be cast to kotlin.jvm.functions.Function1
I know the test can be done having the privateFunc as public and maybe annotating it with #VisibleForTesting, but what I want is to avoid compromising the design as long as I can.
Any ideas? Thanks in advance!
I don't think KFunction and KCallable have any notion of a bound receiver, so they are not invokable (have no operator fun invoke), and therefore don't qualify as functions. So I think you have to wrap the KFunction object in a function to be able to pass it to your higher order function. To call a KFunction, you pass the instance of the receiver class as the first argument.
val serviceMock = MyService()
val sut = SystemUnderTest()
val privateMethod = sut.getPrivateMethod("privateFunc")
service.myHighOrderFunction { privateMethod.call(sut) }
Edit: To internalize the creation of the wrapped function, you could do this:
inline fun <reified T> T.getZeroArgPrivateMethod(name: String): () -> Unit = {
T::class.declaredMemberFunctions.first {
it.name == name
}.apply {
isAccessible = true
}.call(this)
}
//...
val serviceMock = MyService()
val sut = SystemUnderTest()
val privateMethod = sut.getZeroArgPrivateMethod("privateFunc")
service.myHighOrderFunction(privateMethod)
even when I am using the spy method, I am not able to mock the getContext() method of attributesStorage() to get my context.
this is my code :
class Rich
{
fun method1() : HashMap<String,String>
{
val x = attributeStorage().getStore()
return x
}
}
class AttributeStorage
{
private fun getContext()
{
return MyProject.instance.context()
}
fun getStore()
{
//some work done,
return HashMap<String,String>()
}
}
#PrepareForTest(Rich::class)
class RichTest {
#Mock
lateinit var mcontext: Context
fun init()
{
mcontext = Mockito.mock(Context::class.java)
val mAttributesStorage = spy(AttributesStorage())
`when`<Context>(mAttributesStorage,"getContext").thenReturn(mcontext)
Mockito.`when`(mAttributesStorage.getStore()).thenReturn(mapOf("1" to "1"))
}
fun test()
{
//gives an error because the getContext() couldn't be mocked
}
}
I looked at every question possible on stack overflow and went through powermock and mockito documentation but couldn't find a solution to this.
#Mock
lateinit var mcontext: Context
and
mcontext = Mockito.mock(Context::class.java)
are one too many. Use either the one or the other (annotation preferred, of course).
See Shorthand for mocks creation - #Mock annotation:
Important! This needs to be somewhere in the base class or a test runner:
MockitoAnnotations.initMocks(testClass);
Regarding your last code comment: objects are mocked, methods are stubbed.
My classes is written in Kotlin and here is my SharedPreferenceHandler
class SharedPreferenceHandler(sharedPrefs: SharedPreferences) {
companion object {
var mInstance: SharedPreferenceHandler = SharedPreferenceHandler(getPrefs())
private fun getPrefs(): SharedPreferences {
return Application.mInstance.getSharedPreferences(
"myApp", Context.MODE_PRIVATE)
}
fun getInstance(): SharedPreferenceHandler {
return mInstance
}
}
private var sharedPreferences = sharedPrefs
var accessToken: String?
get() = sharedPreferences.getString(SharedPreference.ACCESS_TOKEN.name, null)
set(token) = sharedPreferences.edit().putString(SharedPreference.ACCESS_TOKEN.name, token).apply()
}
Here is method called in presenter:
override fun reload(vm: ViewModel) {
super.updateViewModel(vm) {
//some stuffs
}
}
Here is my test method:
#Test
public void reload() {
when(SharedPreferenceHandler.Companion.getMInstance().getAccessToken()).thenReturn("234234234234234");
presenter.reload(viewModel);
}
In handler from super.updateViewModel(vm) I call "SharedPreferenceHandler.mInstance.accessToken!!)"
That is what is thrown:
Caused by: java.lang.IllegalStateException:
Application.mInstanc…m", Context.MODE_PRIVATE) must not be null
at
com.zuum.zuumapp.preferences.SharedPreferenceHandler$Companion.getPrefs(SharedPreferenceHandler.kt:18)
at
com.zuum.zuumapp.preferences.SharedPreferenceHandler$Companion.access$getPrefs(SharedPreferenceHandler.kt:14)
at
com.zuum.zuumapp.preferences.SharedPreferenceHandler.(SharedPreferenceHandler.kt:15)
I wanna to get accessToken by calling " SharedPreferenceHandler.mInstance.accessToken!!" in my test class.
Is possible to get that in my test method?
You can't use Android SharedPreferences in unit test, but you can mock your method call by this:
Mockito.`when`(SharedPreferenceHandler.mInstance.accessToken).thenReturn("token")
And return what you need.
You should not test your code this way. You should create an interface for class you want to mock:
interface MySharedPreferences {
fun getAccessToken(): String
}
Let your SharedPreferencesHandler implements this interface. Then in your presenter (or other class you want to test) inject dependencies (f.e. by constructor or framework like Dagger/Kodein) into your object. Then there is possibility to easy mock this interface. I assume in #Before you create class you test - and then just pass as param your mocked SharedPreferencesHandler.
Testing things with static dependencies is possible, but is but tricky (and a lot of people consider static dependencies as anti-pattern). How to do it is described here: How to android unit test and mock a static method
Example:
class MyPresenter(val sp: MySharedPreferences) {
/* some code here */
fun validateToken() {
if (sp.getAccessToken() == "") throw new Exception()
}
}
Like you see sp is injected into this class as parameter. Normally you don't create views/presenters etc. directly in code but by DI framework (like Dagger or Kodein). Anyway, static dependencies are not easy testable. Injected interface-dependencies can be mocked, and you operating not on object, but on behaviors (so it's bigger level of abstraction). So, now in your test all you have to do is:
class MyTest() {
#Mock lateinit var sharedPreferencesMock: MySharedPreferences
lateinit var instance: MyPresenter
#Before
fun setUp() {
instance = MyPresenter(sharedPreferencesMock)
}
#Test
fun testSomething() {
`when`(sharedPreferencesMock.getAccessToken()).thenReturn("myAccessToken")
/* here is your test body */
}
}