Hot questions for Using Mockito in kotlinx.coroutines

Top 10 Java Open Source / Mockito / kotlinx.coroutines

Question:

I'm using Kotlin Coroutines and in particular using Retrofit's CoroutineCallAdapterFactory. I'm trying then to unit test a class that in turn utilizes Retrofit interface (GalwayBusService below).

interface GalwayBusService {

    @GET("/routes/{route_id}.json")
    fun getStops(@Path("route_id") routeId: String) : Deferred<GetStopsResponse>

}

In my unit test I have

val galwayBusService = mock()

and then trying something like following to mock what gets returned when that method is called. The issue is though that getStops returns a Deferred value. Is there any particular approach recommend for mocking APIs like this?

`when`(galwayBusService.getBusStops()).thenReturn(busStopsResponse)

Answer:

The proper solution is to use CompletableDeferred. It is better than writing async because it doesn't launch anything concurrently (otherwise your test timings may become unstable) and gives you more control over what happens in what order.

For example, you can write it as whenever(galwayBusService. getBusStops()).thenReturn(CompletableDeferred(busStopsResponse)) if you want to unconditionally return completed deferred or

val deferred = CompletableDeferred<GetStopsResponse>()
whenever(galwayBusService.getBusStops()).thenReturn(deferred)
// Here you can complete deferred whenever you want

if you want to complete it later

Question:

I've begun writing unit tests for my MVP Android project, but my tests dependent on coroutines intermittently fail (through logging and debugging I've confirmed verify sometimes occurs early, adding delay fixes this of course)

I've tried wrapping with runBlocking and I've discovered Dispatchers.setMain(mainThreadSurrogate) from org.jetbrains.kotlinx:kotlinx-coroutines-test, but trying so many combinations hasn't yielded any success so far.

abstract class CoroutinePresenter : Presenter, CoroutineScope {
    private lateinit var job: Job

    override val coroutineContext: CoroutineContext
        get() = job + Dispatchers.Main

    override fun onCreate() {
        super.onCreate()
        job = Job()
    }

    override fun onDestroy() {
        super.onDestroy()
        job.cancel()
    }
}

class MainPresenter @Inject constructor(private val getInfoUsecase: GetInfoUsecase) : CoroutinePresenter() {
    lateinit var view: View

    fun inject(view: View) {
        this.view = view
    }

    override fun onResume() {
        super.onResume()

        refreshInfo()
    }

    fun refreshInfo() = launch {
        view.showLoading()
        view.showInfo(getInfoUsecase.getInfo())
        view.hideLoading()
    }

    interface View {
        fun showLoading()
        fun hideLoading()

        fun showInfo(info: Info)
    }
}

class MainPresenterTest {
    private val mainThreadSurrogate = newSingleThreadContext("Mocked UI thread")

    private lateinit var presenter: MainPresenter
    private lateinit var view: MainPresenter.View

    val expectedInfo = Info()

    @Before
    fun setUp() {
        Dispatchers.setMain(mainThreadSurrogate)

        view = mock()

        val mockInfoUseCase = mock<GetInfoUsecase> {
            on { runBlocking { getInfo() } } doReturn expectedInfo
        }

        presenter = MainPresenter(mockInfoUseCase)
        presenter.inject(view)
        presenter.onCreate()
    }

    @Test
    fun onResume_RefreshView() {
        presenter.onResume()

        verify(view).showLoading()
        verify(view).showInfo(expectedInfo)
        verify(view).hideLoading()
    }

    @After
    fun tearDown() {
        Dispatchers.resetMain()
        mainThreadSurrogate.close()
    }
}

I believe the runBlocking blocks should be forcing all child coroutineScopes to run on the same thread, forcing them to complete before moving on to verification.


Answer:

In CoroutinePresenter class you are using Dispatchers.Main. You should be able to change it in the tests. Try to do the following:

  1. Add uiContext: CoroutineContext parameter to your presenters' constructor:

    abstract class CoroutinePresenter(private val uiContext: CoroutineContext = Dispatchers.Main) : CoroutineScope {
    private lateinit var job: Job
    
    override val coroutineContext: CoroutineContext
        get() = uiContext + job
    
    //...
    }
    
    class MainPresenter(private val getInfoUsecase: GetInfoUsecase, 
                        private val uiContext: CoroutineContext = Dispatchers.Main 
    ) : CoroutinePresenter(uiContext) { ... }
    
  2. Change MainPresenterTest class to inject another CoroutineContext:

    class MainPresenterTest {
        private lateinit var presenter: MainPresenter
    
        @Mock
        private lateinit var view: MainPresenter.View
    
        @Mock
        private lateinit var mockInfoUseCase: GetInfoUsecase
    
        val expectedInfo = Info()
    
        @Before
        fun setUp() {
            // Mockito has a very convenient way to inject mocks by using the @Mock annotation. To
            // inject the mocks in the test the initMocks method needs to be called.
            MockitoAnnotations.initMocks(this)
    
            presenter = MainPresenter(mockInfoUseCase, Dispatchers.Unconfined) // here another CoroutineContext is injected 
            presenter.inject(view)
            presenter.onCreate()
    }
    
        @Test
        fun onResume_RefreshView() = runBlocking {
            Mockito.`when`(mockInfoUseCase.getInfo()).thenReturn(expectedInfo)
    
            presenter.onResume()
    
            verify(view).showLoading()
            verify(view).showInfo(expectedInfo)
            verify(view).hideLoading()
        }
    }
    

Question:

Let's say there's an interface with a callback:

interface SomeInterface {
    fun doSomething(arg: String, callback: (Exception?, Long) -> Unit)
}

which I extend into a suspend function like this:

suspend fun SomeInterface.doSomething(arg: String): Long = suspendCoroutine { cont ->
    this.doSomething(arg) { err, result ->
        if (err == null) {
            cont.resume(result)
        } else {
            cont.resumeWithException(err)
        }
    }
}

I'd like to mock this in tests, but am failing. Ideally I'd like to use something like this:

@Test
fun checkService() {
    runBlocking {
        val myService = mock<SomeInterface>()
        whenever(myService.doSomething(anyString())).thenReturn(1234L)
        val result = myService.doSomething("")
        assertEquals(result, 1234L)
    }
}

The above syntax fails with a mockito exception because it's expecting a matcher for the callback.

org.mockito.exceptions.misusing.InvalidUseOfMatchersException: 
Invalid use of argument matchers!
2 matchers expected, 1 recorded:

How can I mock a suspend function like that? If a similar syntax is not possible how can I have the mock call back with the desired arguments such that the suspend variant that is used throughout my code returns the desired result during tests?

Update: It seems it's not possible when it's an extension function. Based on Marko Topolnik's comment, I gather it's because an extension is simply a static function which is out of mockito's capability.

When the suspend function is a member function, then it works as expected, with my original syntax.

Here is a gist with some demo code: https://gist.github.com/mirceanis/716bf019a47826564fa57a77065f2335


Answer:

I suggest using MockK for your tests, which is more coroutine-friendly.

To mock a coroutine, you can use coEvery and returns like below:

val interf = mockk<SomeInterface>()
coEvery { a.doSomething(any()) } returns Outcome.OK

Question:

I'm attempting to add coroutines to our Android application but I'm hitting a snag with our mocking framework. My interface has a suspend function like so:

interface MyInterface {
  suspend fun makeNetworkCall(id: String?) : Response?
}

Here is how I'm attempting to verify the code was executed in my unit test

runBlocking {
  verify(myInterface).makeNetworkCall(Matchers.anyObject())
}

When I do this I'm getting the following error

org.mockito.exceptions.misusing.InvalidUseOfMatchersException: 
Invalid use of argument matchers!
2 matchers expected, 1 recorded:
-> at     com.myproject.MyTest$testFunction$1.invokeSuspend(MyTest.kt:66)

This exception may occur if matchers are combined with raw values:
  //incorrect:
  someMethod(anyObject(), "raw String");
When using matchers, all arguments have to be provided by matchers.
For example:
  //correct:
  someMethod(anyObject(), eq("String by matcher"));

Is there another way we should verify that the appropriate method is being called when using coroutines? Any help would be appreciated.


Answer:

I tried to write similar test using the code you have provided. Initially, I got same error as yours. However, when I used mockito-core v2.23.4, the tests were passed.

Here are quick steps which you can try:

  1. add testCompile "org.mockito:mockito-core:2.23.4" to the dependencies list in your build.gradle file.

  2. Run the tests again, and you should not get similar error.

As Matchers.anyObject() is deprecated, I used ArgumentMatchers.any().

Below you can see the client code:

data class Response(val message: String)

interface MyInterface {
    suspend fun makeNetworkCall(id: String?) : Response?
}

class Client(val  myInterface: MyInterface) {
    suspend fun doSomething(id: String?) {
        myInterface.makeNetworkCall(id)
    }
}

Here is the test code:

class ClientTest {
    var myInterface: MyInterface = mock(MyInterface::class.java)

    lateinit var SUT: Client

    @Before
    fun setUp() {
        SUT = Client(myInterface)
    }

    @Test
    fun doSomething() = runBlocking<Unit> {
        // Act
        SUT.doSomething("123")
        // Verify
        Mockito.verify(myInterface).makeNetworkCall(ArgumentMatchers.any())
    }
}