如何组合带不同上下文接收器的mockk与withClient函数?
问题场景
我用Kotlin的上下文接收器(context receivers)做「本地化依赖注入」传递客户端上下文,生产代码跑起来没问题,但测试用mockk模拟时卡壳了——因为被mock的方法需要ClientProvider上下文,直接写mock代码会编译失败。
先贴下生产代码的核心实现:
// 用withClient传递ClientProvider上下文 fun <T> withClient(client: Client, block: ClientProvider.() -> T) = ClientProvider { client }.block() // 业务类,方法依赖ClientProvider上下文 class MyService { context(ClientProvider) fun methodUsingClient() {} }
测试时直接mock会报错,因为methodUsingClient()必须在ClientProvider上下文里调用:
val myService: MyService = mockk { every { methodUsingClient() } just runs } // 编译失败,提示缺少ClientProvider上下文
我试着写了个mockkWithClient函数想把mockk和withClient结合,但函数本身编译不过:
inline fun <reified T : Any> mockkWithClient(noinline block: context(T) ClientProvider.() -> Unit) = mockk<T> { withClient(mockk(), block) }
编译器报类型不匹配:
Type mismatch. Required: ClientProvider.() → TypeVariable(T) Found: context(T) ClientProvider.() → Unit
解决方法
核心问题是mockk的lambda参数是T.() -> Unit(以被mock的T实例为接收者),而我们需要把这个接收者和ClientProvider上下文结合起来。调整mockkWithClient的block参数类型,让它同时持有T和ClientProvider两个上下文即可。
正确的实现代码:
inline fun <reified T : Any> mockkWithClient(noinline block: context(T, ClientProvider) () -> Unit) = mockk<T> { // 创建mock的Client,用withClient注入上下文 withClient(mockk()) { // 这里已经有ClientProvider上下文,同时把当前的mock实例(this@mockk)作为T上下文传入 block() } }
调用方式和你预想的完全一致,编译运行都正常:
val myService: MyService = mockkWithClient { every { methodUsingClient() } just runs }
为啥原来的写法不行?
- 你原来定义的block是
context(T) ClientProvider.() -> Unit,意思是:这个函数的接收者是ClientProvider,同时还需要T作为额外上下文。但withClient期望的参数是ClientProvider.() -> T,类型完全对不上,所以编译器报错。 - 正确的block类型应该是
context(T, ClientProvider) () -> Unit,表示这个函数需要同时拥有T(被mock的实例)和ClientProvider两个上下文。在withClient的lambda内部,我们已经有了ClientProvider上下文,再加上mockk lambda里的T实例(this@mockk),刚好满足block的上下文要求,自然就能正常调用了。
内容的提问来源于stack exchange,提问作者David Kubecka
相关产品推荐
相关产品推荐

