1. 概述
在 Kotlin 单元测试中,MockK 是一个广泛使用的 mocking 框架。为了保证测试的独立性和可预测性,合理管理 mock 对象的生命周期至关重要。
clearAllMocks()
和 unmockkAll()
是两个常用于清理 mock 状态的核心方法。它们看似功能相近,实则用途不同,用错可能导致测试间污染或 MockKException 踩坑。
本文将深入剖析这两个方法的区别,并结合代码示例说明各自的适用场景。
2. 普通对象 Mock 的行为差异
我们先看这两个方法对普通类实例 mock 的影响。
假设有一个简单的服务类:
class GreetingService {
fun greeting(name: String) = "$name, how are you?"
}
在测试中,我们可以使用 mockk<T>()
创建其 mock 实例并打桩:
val greetingServiceMock = mockk<GreetingService> {
every { greeting(any()) } returns "mocked instance"
}
assertEquals("mocked instance", greetingServiceMock.greeting("Kai"))
✅ 此时调用 greeting("Kai")
返回的是预设值 "mocked instance"
,说明打桩生效。
调用 clearAllMocks()
接下来执行 clearAllMocks()
:
clearAllMocks()
assertThrows<MockKException> {
greetingServiceMock.greeting("Kai")
}.also { it.message?.startsWith("no answer found for: com.baeldung.mockk.GreetingService") }
❌ 抛出异常!因为 clearAllMocks()
会清除所有 mock 对象上的打桩信息(stubs)和调用记录,但 mock 实例本身仍然存在。
此时再调用该方法,由于没有匹配的 stub,MockK 就会报错。
调用 unmockkAll()
换成 unmockkAll()
呢?
unmockkAll()
assertEquals("mocked instance", greetingServiceMock.greeting("Kai"))
✅ 测试通过!这说明:**unmockkAll()
对普通 mock 实例没有任何作用**。
⚠️ 这是一个常见的认知误区 —— 方法名看似“取消所有 mock”,但实际上它只针对静态类、companion object、top-level 函数等特殊类型起作用。
3. 静态类 / Object / 顶层函数 Mock 的行为对比
现在我们来看真正体现两者区别的场景:mockkStatic()
和 mockkObject()
创建的 mock。
扩展之前的 GreetingService
类,添加一个 companion object:
class GreetingService {
fun greeting(name: String) = "$name, how are you?"
companion object {
fun sayGoodDay(name: String) = "$name, good day!"
}
}
定义一个 Kotlin object
:
object GoodMorning {
fun sayGoodMorning(name: String) = "$name, good morning!"
}
再定义一个顶层函数(top-level function),位于 .kt
文件顶层:
fun sayGoodNight(name: String) = "$name, good night!"
3.1 打桩这些静态元素
使用 MockK 提供的方法进行 mock 和 stub:
// Mock companion object
mockkObject(GreetingService)
every { GreetingService.sayGoodDay(any()) } returns "mocked static fun"
assertEquals("mocked static fun", GreetingService.sayGoodDay("Kai"))
// Mock object
mockkObject(GoodMorning)
every { GoodMorning.sayGoodMorning(any()) } returns "mocked object"
assertEquals("mocked object", GoodMorning.sayGoodMorning("Kai"))
// Mock top-level function
mockkStatic(::sayGoodNight)
every { sayGoodNight(any()) } returns "mocked top-level fun"
assertEquals("mocked top-level fun", sayGoodNight("Kai"))
一切正常,打桩成功 ✅
3.2 分别调用 clearAllMocks() 和 unmockkAll()
使用 clearAllMocks()
... // 先完成上述打桩操作
clearAllMocks()
// 再次调用这些函数
assertEquals("Kai, good day!", GreetingService.sayGoodDay("Kai"))
assertEquals("Kai, good morning!", GoodMorning.sayGoodMorning("Kai"))
assertEquals("Kai, good night!", sayGoodNight("Kai"))
✅ 测试通过。说明:
- 所有 stub 被清除
- 调用恢复为原始实现(真实逻辑)
但注意:mock 实例仍然存在,可以重新打桩。
使用 unmockkAll()
... // 同样先完成打桩
unmockkAll()
assertEquals("Kai, good day!", GreetingService.sayGoodDay("Kai"))
assertEquals("Kai, good morning!", GoodMorning.sayGoodMorning("Kai"))
assertEquals("Kai, good night!", sayGoodNight("Kai"))
结果看起来和 clearAllMocks()
一样?表面上是的。
⚠️ 但关键区别在于底层机制。
3.3 核心区别:是否释放 mock 实例
方法 | 行为 |
---|---|
clearAllMocks() |
✅ 清除所有 mock 的状态(如 stubs、call records) ✅ 保留 mock 实例,允许后续复用 |
unmockkAll() |
✅ 清除所有 mock 状态 ✅ 销毁所有 mock 实例,必须重新 mockkObject() 或 mockkStatic() 才能再次使用 |
示例验证:尝试重新打桩
使用 clearAllMocks()
后重打桩:
mockkStatic(::sayGoodNight)
every { sayGoodNight(any()) } returns "mocked top-level fun"
assertEquals("mocked top-level fun", sayGoodNight("Kai"))
clearAllMocks()
// 可以直接重新打桩
every { sayGoodNight(any()) } returns "get mocked again"
assertEquals("get mocked again", sayGoodNight("Kai")) // ✅ 成功
使用 unmockkAll()
后重打桩:
mockkStatic(::sayGoodNight)
every { sayGoodNight(any()) } returns "mocked top-level fun"
assertEquals("mocked top-level fun", sayGoodNight("Kai"))
unmockkAll()
// 尝试重新打桩 ❌ 报错!
assertThrows<MockKException> {
every { sayGoodNight(any()) } returns "mocked again"
}.also {
it.message?.startsWith("Failed matching mocking signature")
}
❌ 抛出 MockKException
,提示找不到对应的 mock signature。
✅ 正确做法是先重新创建 mock:
mockkStatic(::sayGoodNight) // 必须重新 mock
every { sayGoodNight(any()) } returns "mocked again"
assertEquals("mocked again", sayGoodNight("Kai"))
4. 如何选择:clearAllMocks() vs unmockkAll()
总结一下两者的适用场景:
场景 | 推荐方法 | 原因 |
---|---|---|
✅ 普通类 mock(new 出来的实例) | clearAllMocks() |
unmockkAll() 对其无影响,无法清理状态 |
✅ 静态方法 / companion object / object / 顶层函数 mock |
|
⚠️ 若多个测试共用同一静态 mock,不清理可能互相干扰 |
✅ 测试类级别隔离(如 JUnit @BeforeEach / @AfterEach) | clearAllMocks() |
更轻量,性能更好,适合频繁调用 |
✅ 测试套件级别清理(如 @AfterAll)或避免全局状态残留 | unmockkAll() |
彻底还原环境,确保无任何 mock 遗留 |
常见实践建议
- 在
@BeforeEach
或@AfterEach
中优先使用clearAllMocks()
- 在
@AfterAll
或集成测试结束时使用unmockkAll()
做兜底清理 - 如果你 mock 了
System.currentTimeMillis()
或其他全局函数,**务必在测试后调用unmockkAll()
**,否则会影响其他测试
5. 结论
方法 | 清除状态 | 销毁实例 | 适用对象 |
---|---|---|---|
clearAllMocks() |
✅ | ❌ | 所有类型(推荐用于普通 mock) |
unmockkAll() |
✅ | ✅ | 特殊类型(static/object/top-level),需彻底清理时使用 |
简单记忆口诀:
🔁
clearAllMocks()
→ 清空行为,保留身份
🧹unmockkAll()
→ 连根拔起,恢复原状
合理使用这两个方法,能有效避免测试间的副作用,提升测试稳定性和可维护性。
如需查看完整示例代码,可访问 GitHub 仓库:https://github.com/Baeldung/kotlin-tutorials/tree/master/kotlin-mockito