Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

mockk verify lambda was passed in mock

Tags:

kotlin

mockk

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?

like image 925
dsantamaria Avatar asked Aug 30 '20 03:08

dsantamaria


People also ask

How do you verify in MockK?

Inside the verification block (between the opening curly bracket { and closing curly bracket } ), you write the method you want to verify. { navigator. navigateTo("Park") } tells MockK to check if the navigateTo method on the navigator object was called with the argument "Park" .

What is relaxed true in MockK?

A relaxed mock is the mock that returns some simple value for all functions. This allows you to skip specifying behavior for each case, while still stubbing things you need. For reference types, chained mocks are returned. Note: relaxed mocking is working badly with generic return types.

What is mockkStatic?

mockkStatic("com.name.app.Writer") Rather than passing a reference to the class, you pass the class name as a string. You can also choose to pass in a reference to the class, and MockK will figure out the class name. mockkStatic(Writer::class) Like object mocks, static mocks behave like spies.

How do I get the parameter passed to a mock method?

When you’re using Moq to set up a mocked method, you can use Callback () to capture the parameters passed into the mocked method: string capturedJson; mockRepo.Setup (t => t.Save (It.IsAny<string > ())) .Callback ( (string json) => { Console.WriteLine ("Repository.Save (json) called.

What happens to mockk while verifying?

While verifying, MockK switches into an safe-state where the given verify-block can be evaluated safely without any modifications from the outside.

Why isn’t the mocked method intercepting the call?

The mocked method is setup for Get (10), whereas you’re calling ProcessMessage (100), which is why the mocked method isn’t intercepting the call at all (and hence why it’s not invoking the Callback () lambda). This is just a typo. After fixing the problem, the test passes and outputs the following:

Where can I find the documentation for mockk?

Check the series of articles “Mocking is not rocket science” at Kt. Academy describing MockK from the very basics of mocking up to description of all advanced features. springmockk introduced in official Spring Boot Kotlin tutorial quarkus-mockk adds support for mocking beans in Quarkus. Documentation can be found here


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) }
like image 139
Sam Avatar answered Nov 10 '22 22:11

Sam