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

Kotlin中Mock对象时调试与实际测试结果不一致问题排查

问题:Mockk模拟Companion对象时调试正常但运行测试触发真实方法

我用Mockk编写测试用例模拟对象,遇到一个奇怪的问题:在IntelliJ调试测试时,调试窗口能获取到Mock响应,但完整运行测试时,却出现数据库客户端错误(实际执行了真实的数据库方法)。

服务代码

class SendMessageService (
    private val authHelper: AuthHelper
) {
    companion object {
        val targetsDb = TargetsDynamodbService()
    }

    fun handlePushIdRequest(sendMessageRequest: SendMessageRequest): Observable<List<Target>> {
        if (!validAuthorization(sendMessageRequest.serviceKey)) {
            throw ClientErrorException("Invalid Authorization", Response.Status.UNAUTHORIZED)
        }

        return targetsDb.getAllTargetsAsObservable(sendMessageRequest.pushId)
    }

    private fun validAuthorization(serviceKey: ServiceKey): Boolean {
        val authorization = authHelper.getTargetId("service key")

        return authorization == Configuration.SendMessageAuthorization.authorization(serviceKey)
    }
}

测试用例代码

class SendMessageServiceTest : AbstractServiceTest() {
    private val authHelper = AuthHelper(listOf("abc"))

    private val targetsDynamodbService: TargetsDynamodbService = mockk()
    private lateinit var service: SendMessageService

    @BeforeEach
    fun setUp() {
        mockkObject(SendMessageService.Companion)
        every { SendMessageService.Companion.targetsDb } returns targetsDynamodbService
        ConfigurationManager.getConfigInstance().setProperty("sendmessage.authorization.test", "abc")
    }

    @Test
    fun `successfully send pushId request message`() {
        every { targetsDynamodbService.getAllTargetsAsObservable(any()) } answers {
            Observable.just(listOf(Target().apply { pushId = arg<String>(0) }))
        }
        service = SendMessageService(authHelper)

        val response = service.handlePushIdRequests(createMessageRequest()).blockingFirst()
        assertTrue(response.isNotEmpty())
    }


    private fun createMessageRequest() =
        SendMessageRequest(
            pushId = "pushId",
            serviceKey = ServiceKey.TEST,
            title = TextLocalization("en", "nl"),
            body = TextLocalization("en", "nl"),
            silent = false,
            collapseKey = null,
            data = null,
            androidOptions = null,
            iosOptions = null
        )
}

问题原因与修复方案

核心原因

问题出在Companion对象中targetsDb的初始化时机与Mockk的模拟时机不匹配:

  • SendMessageService.Companion里的targetsDb是val类型成员变量,在类加载阶段就会初始化真实的TargetsDynamodbService实例。
  • 测试中setUp方法才执行Mock替换操作,非调试模式下测试运行速度快,替换操作还没完成,业务代码就已经引用了真实的数据库实例;而调试时因为断点停顿,替换操作完成后才执行业务逻辑,所以Mock能生效。

修复方案

方案1:延迟初始化Companion中的targetsDb

修改SendMessageService的Companion对象,用lazy延迟初始化targetsDb,确保第一次调用时才创建实例,此时Mock替换已经完成:

companion object {
    val targetsDb by lazy { TargetsDynamodbService() }
}

方案2:调整Service实例的创建时机

把service = SendMessageService(authHelper)移到setUp方法中,确保Mock替换完成后再创建Service实例:

@BeforeEach
fun setUp() {
    mockkObject(SendMessageService.Companion)
    every { SendMessageService.Companion.targetsDb } returns targetsDynamodbService
    ConfigurationManager.getConfigInstance().setProperty("sendmessage.authorization.test", "abc")
    service = SendMessageService(authHelper) // 移至此处
}

@Test
fun `successfully send pushId request message`() {
    every { targetsDynamodbService.getAllTargetsAsObservable(any()) } answers {
        Observable.just(listOf(Target().apply { pushId = arg<String>(0) }))
    }

    // 注意:原测试方法中的handlePushIdRequests是笔误,对应服务方法应为handlePushIdRequest
    val response = service.handlePushIdRequest(createMessageRequest()).blockingFirst()
    assertTrue(response.isNotEmpty())
}

方案3:改用构造注入(推荐)

这是更优雅的解耦方式,将targetsDb作为构造参数传入SendMessageService,彻底避免静态成员带来的Mock问题:

class SendMessageService (
    private val authHelper: AuthHelper,
    // 提供默认实现,不影响原有业务代码使用
    private val targetsDb: TargetsDynamodbService = TargetsDynamodbService()
) {
    fun handlePushIdRequest(sendMessageRequest: SendMessageRequest): Observable<List<Target>> {
        if (!validAuthorization(sendMessageRequest.serviceKey)) {
            throw ClientErrorException("Invalid Authorization", Response.Status.UNAUTHORIZED)
        }
        return targetsDb.getAllTargetsAsObservable(sendMessageRequest.pushId)
    }

    private fun validAuthorization(serviceKey: ServiceKey): Boolean {
        val authorization = authHelper.getTargetId("service key")
        return authorization == Configuration.SendMessageAuthorization.authorization(serviceKey)
    }
}

测试时直接传入Mock实例,无需再Mock Companion对象:

@BeforeEach
fun setUp() {
    ConfigurationManager.getConfigInstance().setProperty("sendmessage.authorization.test", "abc")
    service = SendMessageService(authHelper, targetsDynamodbService)
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 17:37:41