You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用MockK模拟构造函数传入的回调?以ResponseReceiver为例

问题:MockK测试callbackFlow中的回调参数捕获失败

我有一个包含回调逻辑的Receiver类,在callbackFlow内部创建了Server回调实例并传入ResponseReceiver。现在想用MockK测试这个类,但无法捕获或模拟ResponseReceiver(any())接收的回调参数。

Receiver类代码

class Receiver(private val context: Context) {

    val responses: Flow<Response> = callbackFlow {

        val callback = object : Server {
            override fun broadcast(rsp: Response) {
                trySend(rsp)
            }
        }
        
        val receiver: BroadcastReceiver = ResponseReceiver(callback)

        register(receiver)

        awaitClose { unregister(receiver) }
    }

    private fun register(receiver: BroadcastReceiver) =
        context.registerReceiver(receiver, IntentFilter(CMD))

    private fun unregister(receiver: BroadcastReceiver) = context.unregisterReceiver(receiver)
}

测试代码

@Test
fun given_when_then() = runTest {

    // Prepare
    every { ResponseReceiver(any()) } answers {
        firstArg<Server>().broadcast(mockk())
    }

    // Act
    val testObserver = target.responses.test()

    verify { mockContext.registerReceiver(any(), any()) }

    testObserver
        .assertValue(response)
        .finish()
}

解决方案

你的测试思路存在问题:直接mockResponseReceiver构造函数并触发回调的方式,既没模拟真实广播触发的场景,也没正确捕获到Receiver内部创建的Server实例。下面是两种可行的解决方式:

方式一:Mock构造函数并捕获回调

用mockkConstructor来mockResponseReceiver的构造逻辑,同时捕获传入的Server实例,后续手动模拟广播触发来调用回调:

@Test
fun given_when_then() = runTest {
    val mockResponse = mockk<Response>()
    var capturedServer: Server? = null

    // 开启ResponseReceiver的构造mock
    mockkConstructor(ResponseReceiver::class) {
        // 构造时保存传入的Server实例
        every { ResponseReceiver(any()) } answers {
            capturedServer = firstArg()
            mockk() // 返回一个mock的BroadcastReceiver实例
        }

        // 模拟广播被接收时的逻辑,调用捕获到的Server回调
        every { anyConstructed<ResponseReceiver>().onReceive(any(), any()) } answers {
            capturedServer?.broadcast(mockResponse)
        }

        // 订阅Flow
        val testObserver = target.responses.test()

        // 验证接收器已注册
        verify { mockContext.registerReceiver(any(), any()) }

        // 手动触发广播接收(模拟系统发送广播)
        anyConstructed<ResponseReceiver>().onReceive(mockContext, mockk())

        // 断言Flow收到预期数据
        testObserver
            .assertValue(mockResponse)
            .finish()
    }
}

方式二:重构代码优化可测试性

如果可以修改Receiver类,建议把ResponseReceiver的创建逻辑抽离成依赖,比如通过构造函数传入工厂方法,这样就不用mock构造函数:

// 修改后的Receiver类
class Receiver(
    private val context: Context,
    private val receiverFactory: (Server) -> BroadcastReceiver = ::ResponseReceiver
) {

    val responses: Flow<Response> = callbackFlow {
        val callback = object : Server {
            override fun broadcast(rsp: Response) {
                trySend(rsp)
            }
        }
        val receiver = receiverFactory(callback)
        register(receiver)
        awaitClose { unregister(receiver) }
    }

    // ... 原有注册/注销逻辑不变
}

对应的测试代码会更简洁:

@Test
fun given_when_then() = runTest {
    val mockResponse = mockk<Response>()
    var capturedServer: Server? = null

    // 创建自定义的接收器工厂,捕获Server实例
    val mockReceiverFactory = mockk<(Server) -> BroadcastReceiver>()
    every { mockReceiverFactory.invoke(any()) } answers {
        capturedServer = firstArg()
        mockk {
            every { onReceive(any(), any()) } answers {
                capturedServer?.broadcast(mockResponse)
            }
        }
    }

    // 初始化测试目标时传入自定义工厂
    val target = Receiver(mockContext, mockReceiverFactory)

    // 订阅Flow
    val testObserver = target.responses.test()

    verify { mockContext.registerReceiver(any(), any()) }

    // 触发广播接收
    (mockReceiverFactory.invoke(mockk()) as BroadcastReceiver).onReceive(mockContext, mockk())

    testObserver
        .assertValue(mockResponse)
        .finish()
}

内容的提问来源于stack exchange,提问作者Pause

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 14:40:34