kotlinmockk

Matching list using mockk in Kotlin ignoring items order


I have a Kotlin code like this. Using Mockk for testing.

class BugRepository {
    fun fetchAll(ids: List<Long>): List<Bug> { ... some fetching code ... } 
}

Now I have BugService using that repository and I have BugServiceTest that uses code like this:

val mocks = listOf(Bug(...), Bug(...))
every { bugRepository.fetchAll(mocks.map { it.id }) } returns mocks

How could I improve that, so the order of the ids passed to fetchAll method does not matter? Is there any mockk matcher for that?


Solution

  • Alas, the up to now accepted answer is not completely correct.

    Suppose you have

    val mocks = listOf(Bug(1), Bug(2))
    

    The matcher in the solution does accept every map that contains all ids of mocks, but it is not at all a problem if there are even some more elements. That means, that the every clause is matched for listOf(1, 2) and listOf(2, 1), as it should, but also for for listOf(1, 2, 3).

    If you can be sure that there are no duplicates in mocks and the supplied list in the test, the matcher can simply be improved using equality of sets:

    every {
        bugRepository.fetchAll(
            match { list -> list.toSet() == mocks.map { it.id }.toSet() }
        )
    } returns mocks
    

    But this will not work, if there are any duplicates, e.g. listOf(Bug(1), Bug(1)) will be regarded equal to listOf(Bug(1)).

    If you want to take into account also the existence of duplicates, that can be done with a custom matching function that groups all duplicates and compares these in the two lists:

        fun <T> List<T>.containsExactlyInAnyOrder(list: List<T>): Boolean {
            val duplicates = groupBy { it }
            val listDuplicates = list.groupBy { it }
            return duplicates.size == listDuplicates.size &&
                    duplicates.all { listDuplicates[it.key] == it.value }
        }
    

    Then you can use this inside your every clause:

    every {
        bugRepository.fetchAll(
            match { list -> list.containsExactlyInAnyOrder(mocks.map { it.id }) }
        )
    } returns mocks