We will work on the following piece of code. It’s simplified version of Presenter in Model-View-Presenter pattern. Let’s see what we can and should unit test there.
interface View { fun displayItems(list: List<Element>) fun displayDetails(element: Element) fun displayError()}data class Element(val id: Int, val content: String)interface DataProvider { fun getAll(): List<Element> fun getOne(id: Int): Element?}class Presenter( val view: View, val provider: DataProvider) { fun start() { with(provider.getAll()) { if (this.isNotEmpty()) { view.displayItems(this) } else { view.displayError() } } } fun getOne(id: Int) { provider.getOne(id)?.let { view.displayDetails(it) } }}
We will be performing tests here on mocked View with stubbed DataProvider methods:
- when DataProvider.getAll() returns non-empty list of elements, then we display that list on View
- when DataProvider.getAll() returns empty list, we display error on View
- when View asks presenter for element described by given id we display that element if we have it in our repository, otherwise we display error
We will check only the first test case – happy path of displaying two elements on view.
Other test cases are left as an exercise to the reader
Test using vanilla Mockito
import org.junit.Testimport org.junit.runner.RunWithimport org.mockito.Mockimport org.mockito.Mockitoimport org.mockito.junit.MockitoJUnitRunner@RunWith(MockitoJUnitRunner::class)class PresenterTest { @Mock lateinit var view: View @Mock lateinit var dataProvider: DataProvider @Test fun `display non-empty list`() { val elements = listOf( Element(1, "first"), Element(2, "second") ) Mockito.`when`(dataProvider.getAll()).thenReturn(elements) val presenter = Presenter(view, dataProvider) presenter.start() Mockito.verify(view).displayItems(elements) }}
The test flow here consists of the following steps:
- we declare class-level mocks with @Mock annotation and lateinit var modifier
- then we configure DataProvider stub with Mockito.`when`(…).thenReturn(…) function
- we execute system under test entry method (presenter.start())
- we verify with Mockito that given elements were displayed on view with Mockito.verify(…) method
To make @Mock annotations discoverable by framework, we must add MockitoJunitRunner to the whole test class.
Other way of configuring mocks would be declaring them directly in test method, instead of top of the class:
import org.junit.Testimport org.mockito.Mockitoclass PresenterTest { @Test fun `display non-empty list`() { val elements = listOf( Element(1, "first"), Element(2, "second") ) val dataProvider: DataProvider = Mockito.mock(DataProvider::class.java) Mockito.`when`(dataProvider.getAll()).thenReturn(elements) val view: View = Mockito.mock(View::class.java) val presenter = Presenter(view, dataProvider) presenter.start() Mockito.verify(view).displayItems(elements) }}
We got rid of @Mock and @RunWith annotations – and also we can now declare those parameters as val instead of lateinit var.
Yet, mock configuration could me more Kotlin idiomatic.
Let’s try to make use of Mockito-Kotlin extensions
Refactoring with Mockito-Kotlin extensions
import com.nhaarman.mockitokotlin2.doReturnimport com.nhaarman.mockitokotlin2.mockimport org.junit.Testimport org.mockito.Mockitoclass PresenterTest { @Test fun `display non-empty list mockito-kotlin`() { val elements = listOf( Element(1, "first"), Element(2, "second") ) val dataProvider: DataProvider = mock { on { getAll() } doReturn elements } val view: View = mock() val presenter = Presenter(view, dataProvider) presenter.start() Mockito.verify(view).displayItems(elements) }}
Let’s look at method mock{} definition from com.nhaarman.mockitokotlin2.mock package:
As we can see, it’s reified method – type returned by that method will be inferred in proper context, so without passing extra type information we can write:
val dataProvider: DataProvider = mock()
Last argument of that method is stubbing: KStubbing<T>.(T)->Unit. In Kotlin, last functional argument of method could be reduced to just opening lambda in declaration:
val dataProvider: DataProvider = mock {}
Now let’s see how we can make use of KStubbing method:
It will delegate our stubbing logic to regular Mockito calls.
val dataProvider: DataProvider = mock { on { getAll() }}
Then we can introduce more Kotlin-specific invocations by using infix fun doReturn:
doReturn is infix fun – we can use it without „()”:
val dataProvider: DataProvider = mock { on { getAll() }.doReturn(listOf(Element(1, "first)))}// or using infix notation:val dataProvider: DataProvider = mock { on { getAll() } doReturn listOf(Element(1, "first))}
And finally our test will look like this:
import com.nhaarman.mockitokotlin2.doReturnimport com.nhaarman.mockitokotlin2.mockimport org.junit.Testimport org.mockito.Mockitoclass PresenterTestVanillaMockito { @Test fun `display non-empty list`() { val elements = listOf( Element(1, "first"), Element(2, "second") ) val view: View = mock() val dataProvider: DataProvider = mock { on { getAll() } doReturn elements } val presenter = Presenter(view, dataProvider) presenter.start() Mockito.verify(view).displayItems(elements) }}
Summary
Mockito-Kotlin extensions are fairly easy and fun to use – they introduce DSL for mocks and stubs, increasing readability and helping avoid long complex mock configurations in test suite.
Resources
https://github.com/mockito/mockito
https://github.com/mockito/mockito-kotlin