如何通过Mock外部依赖实现Kotlin对象表达式方法的单元测试
问题根源
- Kotlin 中
object关键字声明的是全局单例类,类加载时就会自动执行init代码块完成初始化,整个生命周期只会存在一个实例 @InjectMock的原理是主动创建目标类的新实例再注入 mock 依赖,对全局单例的object类不生效,且原类的init块会在测试加载类时就尝试连接真实数据库,直接运行会报错
解决方案1:使用 MockK 框架测试(推荐,适配 Kotlin 原生特性)
MockK 对 Kotlin 单例、扩展函数等特性的支持度远高于 Mockito,无需修改原有业务代码即可完成测试,步骤如下:
- 引入测试依赖(以 Gradle 为例):
testImplementation "io.mockk:mockk:1.13.8" testImplementation "junit:junit:4.13.2"
- 单元测试实现代码:
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
相关产品推荐
相关产品推荐

