mockk 验证 lambda 是否在 mock 中传递
mockk verify lambda was passed in mock
我正在尝试测试方法 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) }
}
基本上我正在尝试测试是否使用正确的 lambda 作为参数调用了 mock
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) } }
}
}
但是测试失败
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: +
结果解析器
class ResultParser @Inject constructor() {
suspend fun <T> parse(call: suspend () -> Response<T>): Result<T> {
...
}
}
歌曲服务
interface SongsService {
@GET("search")
suspend fun getSongsList(
@Query("term") query: String,
@Query("mediaType") mediaType: String = "music"
): Response<SongsResponse>
}
我不明白为什么会失败。我做错了什么?
函数总是通过标识进行比较
该语言不知道如何将一个函数的内容与另一个函数的内容进行比较。在不同位置创建的两个 lambda 函数,即使它们做完全相同的事情,也不会被视为彼此相等。
您可以用一个简单的例子来证明这一点:
val a = { "Hello, World!" }
val b = { "Hello, World!" }
println(a == b) // prints 'false'
您的 verify
调用失败,因为您实际上有两个不同的 lambda 函数,即使它们包含相同的代码。在 SongsRemoteDataSource
中创建的 lambda 与在 SongsRemoteDataSourceTest
中创建的是不同的对象,因此 MockK 认为它们彼此不相等。
测试 lambda 函数内容的唯一真正方法是 运行 它并查看它的作用。为此,您有几个选择。
使用 answers
到 运行 lambda 函数
解决此问题的一种方法是将模拟 ResultParser
配置为始终 运行 它接收的 lambda 函数。
coEvery {
resultParser.parse(any())
} coAnswers {
firstArg<(suspend () -> Response<SongsResponse>)>().invoke()
}
现在,每次调用 ResultParser
时,它都会立即 运行 它接收到的任何 lambda 函数作为输入。然后,在您调用 getSongsList
之后,您可以验证是否调用了 SongsService
。
songsRemoteDataSource.getSongsList(query)
coVerify {
resultParser.parse(any())
songsService.getSongsList(query)
}
捕获 lambda 函数并运行自己
如果您想更加明确,可以改为捕获 lambda。这使您可以将 lambda 函数分配给一个变量,您可以在其中做您想做的事。您仍然无法比较它是否相等,但您仍然可以 运行 它并测试它的作用。
首先创建一个 slot
来保存捕获的函数。然后将 coVerify
与插槽一起用作参数匹配器。
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) }
我正在尝试测试方法 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) }
}
基本上我正在尝试测试是否使用正确的 lambda 作为参数调用了 mock
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) } }
}
}
但是测试失败
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: +
结果解析器
class ResultParser @Inject constructor() {
suspend fun <T> parse(call: suspend () -> Response<T>): Result<T> {
...
}
}
歌曲服务
interface SongsService {
@GET("search")
suspend fun getSongsList(
@Query("term") query: String,
@Query("mediaType") mediaType: String = "music"
): Response<SongsResponse>
}
我不明白为什么会失败。我做错了什么?
函数总是通过标识进行比较
该语言不知道如何将一个函数的内容与另一个函数的内容进行比较。在不同位置创建的两个 lambda 函数,即使它们做完全相同的事情,也不会被视为彼此相等。
您可以用一个简单的例子来证明这一点:
val a = { "Hello, World!" }
val b = { "Hello, World!" }
println(a == b) // prints 'false'
您的 verify
调用失败,因为您实际上有两个不同的 lambda 函数,即使它们包含相同的代码。在 SongsRemoteDataSource
中创建的 lambda 与在 SongsRemoteDataSourceTest
中创建的是不同的对象,因此 MockK 认为它们彼此不相等。
测试 lambda 函数内容的唯一真正方法是 运行 它并查看它的作用。为此,您有几个选择。
使用 answers
到 运行 lambda 函数
解决此问题的一种方法是将模拟 ResultParser
配置为始终 运行 它接收的 lambda 函数。
coEvery {
resultParser.parse(any())
} coAnswers {
firstArg<(suspend () -> Response<SongsResponse>)>().invoke()
}
现在,每次调用 ResultParser
时,它都会立即 运行 它接收到的任何 lambda 函数作为输入。然后,在您调用 getSongsList
之后,您可以验证是否调用了 SongsService
。
songsRemoteDataSource.getSongsList(query)
coVerify {
resultParser.parse(any())
songsService.getSongsList(query)
}
捕获 lambda 函数并运行自己
如果您想更加明确,可以改为捕获 lambda。这使您可以将 lambda 函数分配给一个变量,您可以在其中做您想做的事。您仍然无法比较它是否相等,但您仍然可以 运行 它并测试它的作用。
首先创建一个 slot
来保存捕获的函数。然后将 coVerify
与插槽一起用作参数匹配器。
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) }