Search code examples
androidunit-testingkotlinkotlinx.coroutines

Kotlin coroutines: Switching context when testing an Android Presenter


I've started using kotlin coroutines in my Android project recently, but I have somewhat of a problem with it. Many would call it a code smell.

I'm using an MVP architecture where the coroutines are started in my presenter like this:

// WorklistPresenter.kt
...
override fun loadWorklist() {
    ...
    launchAsync { mViewModel.getWorklist() }
    ...

The launchAsyncfunction is implemented this way (in my BasePresenter class that my WorklistPresenter class extends):

@Synchronized
protected fun launchAsync(block: suspend CoroutineScope.() -> Unit): Job {
    return launch(UI) { block() }
}

The problem with this is that I'm using a UI coroutine context that depends on the Android Framework. I can't change this to another coroutine context without running into ViewRootImpl$CalledFromWrongThreadException. To be able to unit test this I've created a copy of my BasePresenter with a different implementation of launchAsync:

protected fun launchAsync(block: suspend CoroutineScope.() -> Unit): Job {
    runBlocking { block() }
    return mock<Job>()
}

To me this is a problem because now my BasePresenter has to be maintained in two places. So my question is. How can I change my implementation to support easy testing?


Solution

  • I’d recommend to extract the launchAsync logic into a separate class, which you can simply mock in your tests.

    class AsyncLauncher{
    
        @Synchronized
        protected fun execute(block: suspend CoroutineScope.() -> Unit): Job {
            return launch(UI) { block() }
        }
    
    }
    

    It should be part of your activity constructor in order to make it replaceable.