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?
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" .
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.
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.
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.
While verifying, MockK switches into an safe-state where the given verify-block can be evaluated safely without any modifications from the outside.
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:
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
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.
answers
to run the lambda functionOne 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)
}
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) }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With