mockk verify lambda was passed in mock

Viewed 4128

I'm trying to test the method getSongsList

class SongsRemoteDataSource @Inject constructor(
    private val resultParser: ResultParser,
    private val songsService: SongsService
) {

    suspend fun getSongsList(query: String): Result<SongsResponse> =
        resultParser.parse { songsService.getSongsList(query) }
}

Basically I'm trying to test that the mock was called with the correct lambda as argument

class SongsRemoteDataSourceTest {

    @RelaxedMockK
    private lateinit var resultParser: ResultParser
    @RelaxedMockK
    private lateinit var songsService: SongsService

    private lateinit var songsRemoteDataSource: SongsRemoteDataSource

    @Before
    fun setUp() {
        MockKAnnotations.init(this)
        songsRemoteDataSource = SongsRemoteDataSource(resultParser, songsService)
    }

    @Test
    fun getSongsList() = runBlockingTest {
        val query = "query"

        songsRemoteDataSource.getSongsList(query)

        coVerify { resultParser.parse { songsService.getSongsList(query) } }
    }
}

But the test fails

java.lang.AssertionError: Verification failed: call 1 of 1: ResultParser(resultParser#1).parse(eq(continuation {}), any())). Only one matching call to ResultParser(resultParser#1)/parse(Function1, Continuation) happened, but arguments are not matching:
[0]: argument: continuation {}, matcher: eq(continuation {}), result: -
[1]: argument: continuation {}, matcher: any(), result: +

ResultParser

class ResultParser @Inject constructor() {

    suspend fun <T> parse(call: suspend () -> Response<T>): Result<T> {
        ...
    }
}

SongsService

interface SongsService {

    @GET("search")
    suspend fun getSongsList(
        @Query("term") query: String,
        @Query("mediaType") mediaType: String = "music"
    ): Response<SongsResponse>
}

I can't understand why it fails. What am I doing wrong?

1 Answers

Functions are always compared by identity

The language doesn't know how to compare the contents of one function with another. Two lambda functions created in different places, even if they do the exact same thing, aren't considered equal to each other.

You can demonstrate this with a simple example:

val a = { "Hello, World!" }
val b = { "Hello, World!" }
println(a == b) // prints 'false'

Your verify call is failing because you actually have two different lambda functions, even though they contain the same code. The lambda that gets created in the SongsRemoteDataSource is a different object from the one that gets created in the SongsRemoteDataSourceTest, and so MockK sees them as not equal to each other.

The only real way to test the contents of a lambda function is to run it and see what it does. To do that, you have a couple of options.

Use answers to run the lambda function

One way you could get around this is by configuring your mock ResultParser to always run the lambda function it receives.

coEvery { 
    resultParser.parse(any()) 
} coAnswers { 
    firstArg<(suspend () -> Response<SongsResponse>)>().invoke() 
}

Now, every time the ResultParser is called, it will immediately run whatever lambda function it received as input. Then, after you call getSongsList, you can verify that the SongsService was called.

songsRemoteDataSource.getSongsList(query)
coVerify { 
    resultParser.parse(any())
    songsService.getSongsList(query)
}

Capture the lambda function and run it yourself

If you want to be even more explicit, you could capture the lambda instead. This lets you assign the lambda function to a variable, where you can do what you want with it. You still won't be able to compare it for equality, but you can still run it and test what it does.

First you create a slot that will hold the captured function. Then you use coVerify with the slot as an argument matcher.

val lambdaSlot = slot<(suspend () -> Response<SongsResponse>)>()

songsRemoteDataSource.getSongsList(query)
coVerify { resultParser.parse(capture(lambdaSlot)) }

lambdaSlot.captured.invoke() // runs the lambda function
coVerify { songsService.getSongsList(query) }
Related