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:
- It depends upon the Android SDK
- It depends upon a server, and that server might not always be running at the time we would want to run the tests
- It depends upon content that does not exist in an isolated test, such as some sort of authentication token representing a logged-in user
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:
- It pretends to be an Android
Context - It pretends to be your repository that ordinarily would talk to some server
- It pretends to be an authenticated user
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:
- Create a
TestCoroutineDispatcher - Call
pauseDispatcher()on theTestCoroutineDispatcher - Call
Dispatchers.setMain()before the test to haveDispatchers.Mainpoint to thisTestCoroutineDispatcher - Call
Dispatchers.resetMain()after the test to remove the reference to theTestCoroutineDispatcher - Clean up the
TestCoroutineDispatcherafter the test
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:
- Use
runCurrent()to execute any coroutines that might have been invoked by creating ourSingleModelMotorin initializers or its constructor - Use
runBlockingTest()to execute a coroutine that usesfirst()to get the first viewstate and confirm that it equals an expected value
Prev Table of Contents Next
This book is licensed under the Creative Commons Attribution-ShareAlike 4.0 International license.