You need to enable JavaScript to run this app. 如何通过Mock外部依赖实现Kotlin对象表达式方法的单元测试
优惠活动
大模型
产品
解决方案
定价
更多

如何通过Mock外部依赖实现Kotlin对象表达式方法的单元测试

问题根源
  • Kotlin 中 object 关键字声明的是全局单例类,类加载时就会自动执行 init 代码块完成初始化,整个生命周期只会存在一个实例
  • @InjectMock 的原理是主动创建目标类的新实例再注入 mock 依赖,对全局单例的 object 类不生效,且原类的 init 块会在测试加载类时就尝试连接真实数据库,直接运行会报错

解决方案1:使用 MockK 框架测试(推荐,适配 Kotlin 原生特性)

MockK 对 Kotlin 单例、扩展函数等特性的支持度远高于 Mockito,无需修改原有业务代码即可完成测试,步骤如下:

  1. 引入测试依赖(以 Gradle 为例):
testImplementation "io.mockk:mockk:1.13.8"
testImplementation "junit:junit:4.13.2"
  1. 单元测试实现代码:
import io.mockk.*
import org.junit.After
import org.junit.Before
import org.junit.Test
import java.sql.Connection
import java.sql.PreparedStatement
import java.sql.ResultSet

class DbServiceTest {
    private val mockConnection: Connection = mockk()
    private val mockPreparedStatement: PreparedStatement = mockk()
    private val mockResultSet: ResultSet = mockk()

    @Before
    fun setUp() {
        // Mock 全局单例 DbService
        mockkObject(DbService, recordPrivateCalls = true)
        // Mock 私有初始化方法,避免执行真实数据库连接逻辑
        every { DbService["makeConnection"]() } just Runs
        // 反射替换私有 connection 字段为 mock 对象
        val connectionField = DbService::class.java.getDeclaredField("connection")
        connectionField.isAccessible = true
        connectionField.set(DbService, mockConnection)
    }

    @Test
    fun testCheckConnection() {
        // 定义 mock 方法行为
        every { mockConnection.prepareStatement("SHOW DATABASES;") } returns mockPreparedStatement
        every { mockPreparedStatement.executeQuery() } returns mockResultSet
        // 模拟结果集返回2条数据
        every { mockResultSet.next() } returnsMany listOf(true, true, false)
        every { mockResultSet.getObject(1) } returnsMany listOf("information_schema", "Users")

        // 执行待测试方法
        DbService.checkConnection()

        // 验证方法调用符合预期
        verify(exactly = 1) { mockConnection.prepareStatement("SHOW DATABASES;") }
        verify(exactly = 1) { mockPreparedStatement.executeQuery() }
        verify(exactly = 3) { mockResultSet.next() }
        confirmVerified(mockConnection, mockPreparedStatement, mockResultSet)
    }

    @After
    fun tearDown() {
        // 清除单例 mock 状态,避免影响其他测试用例
        unmockkAll()
    }
}

解决方案2:调整代码结构提升可测试性(无需反射)

如果不想在测试中使用反射,可以给单例类增加测试专用的依赖注入入口,业务代码调整如下:

import androidx.annotation.VisibleForTesting

object DbService {
    private lateinit var connection: Connection

    // 仅测试用的依赖注入入口
    @VisibleForTesting
    fun setConnectionForTest(conn: Connection) {
        connection = conn
    }
    
    init {
        makeConnection()
    }
    
    private fun makeConnection(){
        Class.forName("com.mysql.cj.jdbc.Driver")
        try{
            connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/Users", "user", "password")
        }catch (ex: Exception){
            println(ex)
        }
    }

    fun checkConnection(){
        var preparedStatement: PreparedStatement = connection.prepareStatement("SHOW DATABASES;")
        var resultSet = preparedStatement.executeQuery()
        while(resultSet.next()){
            println(resultSet.getObject(1))
        }
    }
}

调整后测试代码可直接调用 setConnectionForTest 注入 mock 的 Connection 对象,兼容 Mockito、MockK 等所有测试框架。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.13 23:21:02