I was testing my DAO class generated using the Room
library from Architecture Components
. I wanted to check that the LiveData
returned by a query joining multiple tables was going to be updated when data changes.
I started out by using InOrder
verification, but found that whatever argument I wanted to assert, Mockito would say that the method was invoked with a different one (and when I changed the assertion to that one, it would say it was the other one).
Using an ArgumentCaptor
turned out to work fine for this purpose, which is the subject of this question:
ArgumentCaptor
verification work here, but InOrder
does not?Looking at the answers to question on how to verify multiple method calls with different params, both methods should work fine.
Here's a simplified version of my test that showcases the issue:
package com.example
import com.nhaarman.mockito_kotlin.argumentCaptor
import com.nhaarman.mockito_kotlin.check
import com.nhaarman.mockito_kotlin.mock
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.*
import org.mockito.junit.MockitoJUnitRunner
@Suppress("IllegalIdentifier")
@RunWith(MockitoJUnitRunner::class)
class MultipleCallbacksVanillaTest {
private val java = Language("Java")
private val javascript = Language("JavaScript")
private val kotlin = Language("Kotlin")
private val firstList = emptyList<Language>()
private val secondList = listOf(java)
private val thirdList = listOf(java, javascript, kotlin)
private val lastList = listOf(java, kotlin)
@Test fun `using argument captor`() {
// given
val observer = mock<Observer<List<Language>>>()
val liveData = MutableLiveData<List<Language>>()
// when
liveData.observeForever(observer)
liveData.value = firstList
liveData.value = secondList
liveData.value = thirdList
liveData.value = lastList
// then
argumentCaptor<List<Language>>().run {
verify(observer, times(4)).onChanged(capture())
val (firstValue, secondValue, thirdValue, lastValue) = allValues
assertEquals(firstList, firstValue)
assertEquals(secondList, secondValue)
assertEquals(thirdList, thirdValue)
assertEquals(lastList, lastValue)
}
}
@Test fun `using in order`() {
// given
val observer = mock<Observer<List<Language>>>()
val liveData = MutableLiveData<List<Language>>()
// when
liveData.observeForever(observer)
liveData.value = firstList
liveData.value = secondList
liveData.value = thirdList
liveData.value = lastList
// then
inOrder(observer).run {
verify(observer).onChanged(check { assertEquals(firstList, it) })
verify(observer).onChanged(check { assertEquals(secondList, it) })
verify(observer).onChanged(check { assertEquals(thirdList, it) })
verify(observer).onChanged(check { assertEquals(lastList, it) })
}
verifyNoMoreInteractions(observer)
}
}
data class Language(val name: String)
interface Observer<in T> {
fun onChanged(value: T?)
}
class MutableLiveData<T : Any> {
var value: T
get() = _value
set(value) {
observers.forEach { it.onChanged(value) }
_value = value
}
private lateinit var _value: T
private var observers = mutableSetOf<Observer<T>>()
fun observeForever(observer: Observer<T>) {
if (::_value.isInitialized) observer.onChanged(_value)
observers.add(observer)
}
}
using argument captor
passes, but using in order
fails with a message:
java.lang.AssertionError:
Expected :[]
Actual :[Language(name=Java)]
TL;DR - This appears to be a bug and/or bad documentation on Mockito-Kotlin's part, in terms of its check
function.
Mockito-Kotlin's wiki says:
If you want to do more assertions on the received argument, you can use
check
. [...] If you want your test to fail inside acheck
invocation, you should make sure the body throws an error [...]
The implementation of check
calls Mockito's argThat
, and passes the supplied predicate as an argument. However, the documentation for ArgumentMatcher
states:
The method should never assert if the argument doesn't match. It should only return
false
.
Thus Mockito-Kotlin's documentation is in direct contradiction with this constraint.
I'm not sure how to fix this, but you could just avoid check
entirely for now, and use argThat
directly (returning false
as appropriate, rather than throwing).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With