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
  • clearAllMocks():想保留 mock 实例以便复用
  • unmockkAll():希望彻底清理,防止跨测试污染
⚠️ 若多个测试共用同一静态 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


原始标题:Difference Between clearAllMocks() and unmockkAll() in MockK