Step #6: Adding a MainDispatcherRule

Implicitly, SingleModelMotor uses Dispatchers.Main — that is the default coroutine dispatcher for viewModelScope.launch(). Dispatchers in coroutines control the threads that coroutines run on. In an Android app, Dispatchers.Main says “run this code on the main application thread”.

In JUnit, all tests run on a test thread — and in a unit test, such as this, since we are not running on Android, there is no “Android’s main application thread”. As a side effect, there is no definition of Dispatchers.Main.

We need to do something in our app to teach the coroutines system what to use when we reference Dispatchers.Main in our code. The coroutines testing library that we just added contains a TestCoroutineDispatcher that we can use, but we need to tell the coroutines system to use a TestCoroutineDispatcher for Dispatchers.Main.

Right-click over the com.commonsware.todo.ui package in the test/ source set and choose “New” > “Kotlin File/Class” from the context menu. Fill in MainDispatcherRule as the name, and choose “Class” for the kind. Click “OK” to create the empty class.

This puts MainDispatcherRule in com.commonsware.todo.ui. We did not have much of a choice but to put it there, as that is our one-and-only package, and the new-class “dialog” does not let us choose a different package. However, this class is not strictly tied to the UI classes. So, let’s move it into com.commonsware.todo instead.

To do that, right-click over MainDispatcherRule and choose “Refactor” > “Move” from the context menu. In the “To package:” field, change the package to be com.commonsware.todo, then click “Refactor”. This will move MainDispatcherRule to that package.

Then, replace the contents of MainDispatcherRule with this Kotlin code:

package com.commonsware.todo

import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.test.TestCoroutineDispatcher
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.setMain
import org.junit.rules.TestWatcher
import org.junit.runner.Description

// inspired by https://medium.com/androiddevelopers/easy-coroutines-in-android-viewmodelscope-25bffb605471

class MainDispatcherRule(paused: Boolean) : TestWatcher() {
  val dispatcher =
    TestCoroutineDispatcher().apply { if (paused) pauseDispatcher() }

  override fun starting(description: Description?) {
    super.starting(description)

    Dispatchers.setMain(dispatcher)
  }

  override fun finished(description: Description?) {
    super.finished(description)

    Dispatchers.resetMain()
    dispatcher.cleanupTestCoroutines()
  }
}

In JUnit, a Rule is a standard way to package reusable bits of test logic, particularly related to common test configuration. Here, we define our own custom rule, by extending TestWatcher. MainDispatcherRule says “do threading differently for coroutines in this test”.

Specifically, we create a TestCoroutineDispatcher and use that for Dispatchers.Main. starting() is called on our TestWatcher when a test is starting, and there we call Dispatches.setMain() to provide a dispatcher to use for Dispatchers.Main. finished() is called on our TestWatcher when a test is ending, and there we call Dispatchers.resetMain() to reset the Dispatchers.Main definition to its default. We also call cleanupTestCoroutines() on our TestCoroutineDispatcher, to indicate that we are done with this dispatcher and anything still outstanding should be canceled.

If you add this code to your project, Android Studio will have some complaints:

Android Studio Warnings
Android Studio Warnings

If you hover your mouse over those yellow warnings, you will find that the problem is that all of those things are considered “experimental” by JetBrains (the creators of Kotlin). It is possible that these classes and functions will be renamed or even removed in some future version of coroutines. That is a problem for the future — for now, this code will work fine.


Prev Table of Contents Next

This book is licensed under the Creative Commons Attribution-ShareAlike 4.0 International license.