Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ArgumentCaptor vs InOrder to verify subsequent callbacks with different arguments

Tags:

mockito

kotlin

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)]
like image 644
arekolek Avatar asked Nov 07 '22 12:11

arekolek


1 Answers

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

like image 166
Oliver Charlesworth Avatar answered Nov 22 '22 03:11

Oliver Charlesworth