arekolek
arekolek

Reputation: 9621

ArgumentCaptor vs InOrder to verify subsequent callbacks with different arguments

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:

Why does 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)]

Upvotes: 5

Views: 2308

Answers (2)

arekolek
arekolek

Reputation: 9621

Using a check (instead of the one provided by Mockito-Kotlin) function like this seems to work fine:

inline fun <reified T> check(noinline predicate: (T?) -> Unit): T? {
    return Mockito.argThat {
        try {
            predicate(it)
            true
        } catch (e: Throwable) {
            false
        }
    }
}

Update

This issue has been fixed in 2.0.0-alpha04 version of mockito-kotlin.

Upvotes: 0

Oliver Charlesworth
Oliver Charlesworth

Reputation: 272667

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 a check 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).

Upvotes: 1

Related Questions