Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Kotlin Coroutine Testing with Dispatchers.IO

So maybe there has been a tutorial going over this, but none of the ones I have read have addressed this issue for me. I have the structure as below and am trying to unit test, but when I go to test I always fails stating the repo method doSomthing() was never called. My best guess is because i have launched a new coroutine in a different context. How do I test this then?

Repository

interface Repository {
    suspend fun doSomething(): String
}

View Model

class ViewModel(val repo: Repository) {
    val liveData = MutableLiveData<String>()
    fun doSomething {
    //Do something here
        viewModelScope.launch(Dispatchers.IO) {
            val data = repo.doSomething()
            withContext(Dispatchers.Main) {
                liveData.value = data
            }
        }
    }
}

View Model Test

class ViewModelTest {
    lateinit var viewModel: ViewModel
    lateinit var repo: Repository

    @Before
    fun setup() {
        Dispatchers.setMain(TestCoroutineDispatcher())
        repo = mock<Repository>()
        viewModel = ViewModel(repo)
    }

    @Test
    fun doSomething() = runBlockingTest {
        viewModel.doSomething()
        viewModel.liveData.test().awaitValue().assertValue {
            // assert something
        }
        verify(repo).doSomthing()
    }
}
like image 945
Adrian Le Roy Devezin Avatar asked Jan 06 '20 22:01

Adrian Le Roy Devezin


People also ask

What are dispatchers in kotlin coroutines?

Kotlin coroutines use dispatchers to determine which threads are used for coroutine execution. To run code outside of the main thread, you can tell Kotlin coroutines to perform work on either the Default or IO dispatcher. In Kotlin, all coroutines must run in a dispatcher, even when they're running on the main thread.

What is ExperimentalCoroutinesApi?

annotation class ExperimentalCoroutinesApi. Content copied to clipboard. Marks declarations that are still experimental in coroutines API, which means that the design of the corresponding declarations has open issues which may (or may not) lead to their changes in the future.

What is runBlockingTest?

runBlockingTest takes in a block of code and blocks the test thread until all of the coroutines it starts are finished. It also runs the code in the coroutines immediately (skipping any calls to delay ) and in the order they are called–-in short, it runs them in a deterministic order.


2 Answers

According to Google: enter image description here

Dispatchers should be injected into your ViewModels so you can properly test. You are setting the TestCorotutineDispatcher as the main Dispatcher via Dispatchers.setMain which takes control over the MainDispatcher, but you still have no control over the the execution of the coroutine launched via viewModelScope.launch(Dispatchers.IO).

Passing the Dispatcher via the constructor would make sure that your test and production code use the same dispatcher.

Typically an @Rule is defined that:

  1. Overrides the MainDispatcher via Dispatchers.setMain (like you are doing)
  2. Uses the TestCoroutineDispatcher's own runBlockingTest() to actually run the test.

Here is a really nice talk about testing and coroutines that happened at last year's Android Dev Summit.

And here is an example of such an @Rule. (Shameless plug. There are also examples of coroutine tests on that repo as well)

like image 129
Emmanuel Avatar answered Oct 08 '22 18:10

Emmanuel


I write this solution for who use Dagger.

Inject CoroutineDispatcher in ViewModel constructor like this:

class LoginViewModel @Inject constructor(val dispatcher: CoroutineDispatcher) : BaseViewModel() {

and Provide Dispatcher like this:

@Singleton
@Provides
fun provideDispatchers(): CoroutineDispatcher = Dispatchers.IO

and in test package, Provide Dispatcher like this:

@Singleton
@Provides
fun provideDispatchers(): CoroutineDispatcher = UnconfinedTestDispatcher()

and now all lines in viewModelScope.launch(dispatcher) will be run

like image 24
Marjan Davodinejad Avatar answered Oct 08 '22 17:10

Marjan Davodinejad