Employing Mocks

In unit tests, we often run into problems where we want to exercise one thing, but that thing depends upon other things that we lack:

For those things, we can use mocks.

Why Are We Being Mean?

In this case, “mock” does not mean to ridicule something.

Instead, here, “mock” is used to indicate an object that pretends to be something that it is not:

If we set things up properly, our tests can exercise some specific functionality while using mock objects for anything that we cannot readily use in our tests. We can set up the mock objects to respond as we need them to for a given test (“stubs”). We can even verify that those mock objects were used when we expected them to be.

The Importance of Dependency Inversion

Part of the “set things up properly” is needing to ensure we have a way of getting the mocks where we need them. That is where dependency inversion comes into play.

In ToDoTests, we have a ViewModel class called SingleModelMotor. It works with a ToDoRepository to supply the UI with to-do items, plus support actions whereby users can add, edit, or delete to-do items. In the real app, ToDoRepository is backed by a Room-powered database. However, that does not work well in a unit test: Room expects to work with an Android Context object, and Room expects a SQLite implementation that exists on Android but not necessarily on your development machine.

We could use a mock ToDoRepository… but that implies that we have a way to get the mock repository over to our SingleModelMotor that we are testing. If SingleModelMotor was referring to some singleton instance of ToDoRepository — such as having ToDoRepository be a Kotlin object — then we would be out of luck.

Instead, ToDoTests uses Koin for dependency inversion, as we used in a previous chapter. As such, SingleModelMotor gets a ToDoRepository in its constructor, so in testing, we can just supply our mock ToDoRepository when we create a SingleModelMotor instance.

Add Mockito

The leading mock implementation in use for Android is Mockito. A Java project can use Mockito directly, but a Kotlin project usually is better served by using Mockito by means of the Mockito-Kotlin wrapper library.

ToDoTests has testImplementation lines in its dependencies to pull in Mockito and the Mockito-Kotlin wrapper:

  testImplementation "org.mockito:mockito-inline:3.12.1"
  testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0"

Define and Supply a Mock

Declaring a mock in Kotlin is very easy: call the mock() global function supplied by Mockito-Kotlin:

  private val repo: ToDoRepository = mock {
    on { find(testModel.id) } doReturn flowOf(testModel)
  }

The resulting object is not a “real” instance of ToDoRepository, but rather an generated object that offers the same public API.

We can then pass this mock to objects that need it, such as an instance of SingleModelMotor:

  @Before
  fun setUp() {
    underTest = SingleModelMotor(repo, testModel.id)
  }

Here, setUp() will be called before each of our test functions, since it has the @Before annotation.

Define Stub Responses

The lambda expression in our mock() call defines a stub response. Our SingleModelMotor is going to call a find() function on ToDoRepository, and we need to indicate what our mock should return when that call is made. By default, the mock will return null, which is not what we want.

One Mockito-Kotlin recipe for setting up a stub uses on() and doReturn() inside of that mock() lambda expression. on() wraps the API call that we are expecting (repo.find(testModel.id)), where testModel is a test ToDoModel instance:

  private val testModel = ToDoModel("this is a test")

The doReturn() function indicates the value that we want to return when that API call is made. In this case, find() returns a Flow from Kotlin coroutines, and we use the flowOf() top-level function to create a Flow wrapped around our single test model object.

You can learn more about Flow in the "Introducing Flows and Channels" chapter of Elements of Kotlin Coroutines!

The net result of this line is that when SingleModelMotor tries calling find() on our ToDoRepository, and it passes in the stated id value, our mock will return a Flow of our test model object.

Verify Calls

We can even confirm that SingleModelMotor actually calls find() as we are expecting. We do that in the initial state() test function:

  @Test
  fun `actions pass through to repo`() {
    val replacement = testModel.copy("whatevs")

    underTest.save(replacement)
    mainDispatcherRule.dispatcher.runCurrent()

    mainDispatcherRule.dispatcher.runBlockingTest { verify(repo).save(replacement) }

    underTest.delete(replacement)
    mainDispatcherRule.dispatcher.runCurrent()

    mainDispatcherRule.dispatcher.runBlockingTest { verify(repo).delete(replacement) }
  }

The final line of that function starts with verify(). This wraps a mock object with what amounts to another mock, one that is used for verification. When we call functions on the verify() mock, that mock confirm that the “real” mock object received a similar call during our test. If it has not, the test fails.

Using Rules

If we look at the entirety of SimpleModelMotorTest, we see that along with our setUp() function and the test function we have been examining, we have another test function… and a strange @Rule thing:

package com.commonsware.todo.ui

import com.commonsware.todo.MainDispatcherRule
import com.commonsware.todo.repo.ToDoModel
import com.commonsware.todo.repo.ToDoRepository
import com.nhaarman.mockitokotlin2.doReturn
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.verify
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runBlockingTest
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Rule
import org.junit.Test

class SingleModelMotorTest {
  @get:Rule
  val mainDispatcherRule = MainDispatcherRule(paused = true)

  private val testModel = ToDoModel("this is a test")

  private val repo: ToDoRepository = mock {
    on { find(testModel.id) } doReturn flowOf(testModel)
  }

  private lateinit var underTest: SingleModelMotor

  @Before
  fun setUp() {
    underTest = SingleModelMotor(repo, testModel.id)
  }

  @Test
  fun `initial state`() {
    mainDispatcherRule.dispatcher.runCurrent()

    mainDispatcherRule.dispatcher.runBlockingTest {
      val item = underTest.states.first().item

      assertEquals(testModel, item)
    }
  }

  @Test
  fun `actions pass through to repo`() {
    val replacement = testModel.copy("whatevs")

    underTest.save(replacement)
    mainDispatcherRule.dispatcher.runCurrent()

    mainDispatcherRule.dispatcher.runBlockingTest { verify(repo).save(replacement) }

    underTest.delete(replacement)
    mainDispatcherRule.dispatcher.runCurrent()

    mainDispatcherRule.dispatcher.runBlockingTest { verify(repo).delete(replacement) }
  }
}

As mentioned earlier, JUnit rules represent encapsulated code that runs before and after each of our tests. In Kotlin, due to some JUnit4 quirks, we need to use @get:Rule syntax, saying that the @Rule annotation is being applied to the getter function for this property.

You can learn more about annotations on generated accessors in the "Java Interoperability" chapter of Elements of Kotlin!

Sometimes, libraries provide us with rules. For example, InstantTaskExecutorRule is from the Jetpack. It changes the behavior of LiveData such that it always uses the current thread for its work, rather than the Android main application thread. All we would need to do is instantiate and annotate the InstantTaskExecutorRule and we get this behavior. But, this project does not use LiveData, so we do not need that particular rule.

Writing Rules

However, we could use a similar rule for coroutines. SingleModelMotor references Dispatchers.Main, which in Android will map to the main application thread. However, Dispatchers.Main has no meaning in a unit test, as we are not running on Android. We need to define what to use for Dispatchers.Main when running our tests, and ideally we would use something that makes the tests easier to run.

You can learn more about dispatchers in the "Choosing a Dispatcher" chapter of Elements of Kotlin Coroutines!

To that end, the ToDoTests project has a MainDispatcherRule, which we use in SingleModelMotorTest:

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()
  }
}

The simplest way to write a JUnit rule is to extend TestWatcher, then override the starting() and finishing() functions. Those will be called before and after each test, respectively.

In the case of MainDispatcherRule, we:

A TestCoroutineDispatcher is a coroutine dispatcher that operates under manual control, so our test code can indicate when it should process any queued coroutines. And, those coroutines run synchronously, on whatever thread we happen to be on. That is the behavior of TestCoroutineDispatcher when it is in a “paused” state — we put it in that state at the outset via the pauseDispatcher() call.

Then, in our SingleModelMotorTest, we can apply this rule using @get:Rule syntax. Also, we can call runCurrent() on the TestCoroutineDispatcher to indicate that it is time to run any queued coroutines.

Testing StateFlow

The ToDoTests project uses a mix of coroutines and StateFlow for its threading and reactive results. In particular, SingleModelMotor has a states property that is a StateFlow, emitting some viewstates that are created by means of that find() call on our repository.

So the overall flow of the initial state() test function is:


Prev Table of Contents Next

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