theresawalrus
theresawalrus

Reputation: 387

Kotlin Mockk : Unable to mock a list correctly

I am trying to verify that .shuffled() on a list is called, but get an error on running because of a prior .take(6) call on the list, and I cannot see a way around this.

Here is some code that gets the same error:

val mockList =
    mockk<List<String>> { every { shuffled() } returns mockk(relaxed = true) }
val choiceList = spyk(listOf("String1", "String2")) { every { take(6) } returns mockList }

val tmp = choiceList.take(6)
val tmp2 = tmp.shuffled()

verify {mockList.shuffled())

On line 4, I get the following error:

class io.mockk.renamed.java.util.List$Subclass0 cannot be cast to class java.lang.Integer (io.mockk.renamed.java.util.List$Subclass0 is in unnamed module of loader 'app'; java.lang.Integer is in module java.base of loader 'bootstrap')

Attempting to go around by directly verifying on choiceList.take(6).shuffled() and combining the two tmp vals into one has had no success, as it gets true whether or not .shuffled() gets called. Also, switching from a spy to a mock for choiceList has also not worked.

Edit: Note, since this is a toy example, the take() is completely necessary, and cannot be removed, as it has real use in the actual code.

Upvotes: 1

Views: 3261

Answers (2)

Sergey Gerasimov
Sergey Gerasimov

Reputation: 141

Interesting one!

I think in current implementation it is not possible. The easy answer would be "this test misses the declaration of wrapping static class" (as extension methods are just the same as java static methods for JVM). But if we add it...

    @Test
    fun test() {
        mockkStatic("kotlin.reflect.jvm.internal.impl.utils.CollectionsKt")
        val iterClass = mockkClass(Iterable::class)
        val mockList = mockk<List<String>> { every { shuffled() } returns mockk(relaxed = true) }
        with(iterClass) {
            every { take(6) } returns mockList
            val tmp = take(6)
            val tmp2 = tmp.shuffled()
            verify {
                mockList.shuffled()
            }
        }
    }

we have a Recursion detected in a lazy value under LockBasedStorageManager@1d2ad266 (DeserializationComponentsForJava.ModuleData) which is understandable - we just mocked the whole extensions package. And it is not possible to mock only one extension method leaving others intact. (source: https://github.com/mockk/mockk#extension-functions)

However, I'd do the following. Why not make our own extension functions which call the original and mock those? It would go like this:

Main.kt:

package root
...
fun <T> Iterable<T>.take(n: Int): Iterable<T> {
    val m = Iterable<T>::take
    return m.call(this)
}

fun <T> Iterable<T>.shuffled(): Iterable<T> {
    val m = Iterable<T>::shuffled
    return m.call(this)
}

Test.kt:

package root
...
    @Test
    fun test() {
// note this changed
        mockkStatic("root.MainKt")
        val iterClass = mockkClass(Iterable::class)
        val mockList = mockk<List<String>> { every { shuffled() } returns mockk(relaxed = true) }
        with(iterClass) {
            every { take(6) } returns mockList
            val tmp = take(6)
            val tmp2 = tmp.shuffled()
            verify {
                mockList.shuffled()
            }
        }
    }

The only downside here I think is that it's reflection (duh!) So, this can possibly affect performance and has the requirement to have implementation(kotlin("reflect")) in the dependencies (to use call()). If it is not feasible I think there's no clean solution.

Upvotes: 2

Dinosaur-Guy
Dinosaur-Guy

Reputation: 135

    val mockList: List<String> = mockk(relaxed = true)
    mockList.shuffled()
    verify { mockList.shuffled() }

This works for me. The problem is that take of choiceList cannot be mocked somehow. Is that really necessary?

Upvotes: -1

Related Questions