1

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.

theresawalrus
  • 347
  • 2
  • 19

2 Answers2

2

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.

-1
    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?

Dinosaur-Guy
  • 135
  • 1
  • 11
  • The take is absolutely necessary. It's the entire reason I'm having trouble here, and is necessary to the code at large. – theresawalrus Feb 02 '23 at 18:35