使用 Mockito 进行模拟
使用 Mockito 和 MockK 隔离依赖。设置返回值,验证交互,捕获参数,并使用间谍进行部分模拟。
使用 Mockito 进行模拟 是 CoddyKit 上的免费 Android Academy 课时。 这是第 2 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Android Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Android Academy 课程共包含 4 节课。
什么是模拟
Mock 是测试中用于替代真实依赖的伪对象。与 Fake(可运行的内存实现)不同,Mock:
- 由库自动生成
- 记录调用了哪些方法以及传入了哪些参数
- 返回您指定的任何结果(存根设置)
- 允许您在测试后验证交互
Mockito 设置
将 Mockito 和 MockK(以 Kotlin 为先的替代方案)添加到 app/build.gradle:
// app/build.gradle:
testImplementation 'org.mockito:mockito-core:5.10.0'
testImplementation 'org.mockito.kotlin:mockito-kotlin:5.2.1'
// MockK (Kotlin-idiomatic alternative to Mockito):
testImplementation 'io.mockk:mockk:1.13.10'创建 Mock
使用 mock() 或 @Mock 注解创建 Mock:
import org.mockito.kotlin.*
interface UserApi {
suspend fun fetchUsers(): List<User>
suspend fun deleteUser(id: Int)
}
class RepositoryTest {
// Option 1: create inline
private val api: UserApi = mock()
// Option 2: use annotation + MockitoJUnit4Runner
@Mock lateinit var api2: UserApi
@get:Rule
val mockitoRule = MockitoJUnit.rule()
}存根设置 — 定义返回值
使用 whenever(mock.method()).thenReturn(value) 设置 Mock 的返回值:
val api: UserApi = mock()
// Stub a return value:
whenever(api.fetchUsers()).thenReturn(
listOf(User(1, "Alice"), User(2, "Bob"))
)
// Stub an exception:
whenever(api.fetchUsers()).thenThrow(RuntimeException("Network error"))
// Return different values on consecutive calls:
whenever(api.fetchUsers())
.thenReturn(emptyList()) // first call
.thenReturn(listOf(User(1, "Alice"))) // second callverify() — 检查交互
使用 verify() 断言某个方法是否被调用,以及被调用了多少次:
val api: UserApi = mock()
val repo = UserRepository(api)
repo.refreshUsers()
// Verify that fetchUsers() was called exactly once:
verify(api).fetchUsers()
// Verify called with specific argument:
repo.deleteUser(42)
verify(api).deleteUser(42)
// Verify called exactly 3 times:
verify(api, times(3)).fetchUsers()
// Verify never called:
verify(api, never()).deleteUser(any())ArgumentCaptor
ArgumentCaptor 会捕获传递给 Mock 方法的参数,以便您断言其值:
val api: UserApi = mock()
val captor = argumentCaptor<User>()
val repo = UserRepository(api)
repo.saveUser(User(1, "Alice"))
verify(api).saveUser(captor.capture())
val captured = captor.firstValue
assertEquals(1, captured.id)
assertEquals("Alice", captured.name)MockK — 以 Kotlin 为先的模拟库
MockK 专为 Kotlin 设计,可以原生处理扩展函数、协程和对象:
import io.mockk.*
val api = mockk<UserApi>()
// Stub:
every { api.fetchUsers() } returns listOf(User(1, "Alice"))
// Stub suspend function:
coEvery { api.fetchUsers() } returns listOf(User(1, "Alice"))
// Verify:
val repo = UserRepository(api)
runBlocking { repo.refresh() }
coVerify { api.fetchUsers() }使用 MockK 模拟协程
对挂起函数使用 coEvery 和 coVerify:
val api = mockk<UserApi>()
// Stub suspend function:
coEvery { api.fetchUsers() } returns listOf(User(1, "Alice"))
coEvery { api.deleteUser(any()) } just Runs // returns Unit
// Test:
runTest {
val repo = UserRepository(api)
repo.refresh()
coVerify(exactly = 1) { api.fetchUsers() }
coVerify(exactly = 0) { api.deleteUser(any()) }
}Spy — 部分模拟
spy 会包装一个真实对象。除非被覆盖,否则默认调用真实方法:
// Mockito Spy:
val realList = mutableListOf("a", "b", "c")
val spyList = spy(realList)
// Real method called:
spyList.add("d")
assertEquals(4, spyList.size) // real add() was called
// Override specific method:
doReturn(99).`when`(spyList).size
assertEquals(99, spyList.size) // overridden
// MockK Spy:
val spyObj = spyk(RealService())
every { spyObj.expensiveCall() } returns "fake result"何时使用 Mock,何时使用 Fake
实用指南:
- 使用 Fake:需要可运行的内存实现时(DAO、存储库)— 更易阅读,也不需要库
- 使用 Mock:需要验证某个方法是否使用特定参数被调用时(分析事件、API 调用)
- 避免过度使用 Mock:如果所有内容都被模拟,测试验证的将是 Mock,而不是您的逻辑
常见错误
使用模拟时应避免以下问题:
- 模拟值对象 — 应模拟接口或抽象,而不是数据类
- 模拟层次过深 — 如果一个测试需要 5 个 Mock,说明代码耦合过于紧密
- 交互重要时不进行验证 — 不要只设置存根,还要进行验证
- Mockito 与 Kotlin 的默认行为 — Kotlin 参数默认不可为 null;请使用
mockito-kotlin或 MockK,避免出现意外的 null
模拟对象与伴生对象
MockK 可以模拟 Kotlin 对象和伴生对象,而 Mockito 无法做到:
object DateProvider {
fun today(): String = "2024-01-01"
}
// In test:
mockkObject(DateProvider)
every { DateProvider.today() } returns "2099-12-31"
val result = DateProvider.today()
assertEquals("2099-12-31", result)
// Restore after test:
unmockkObject(DateProvider)快速检查
verify() 在 Mockito 或 MockK 测试中的作用是什么?
回顾:使用 Mockito 与 MockK 进行模拟
Mock 可以帮助您隔离并验证行为:
mock()/mockk()— 根据接口或类创建 Mockwhenever(...).thenReturn(...)/every { } returns ...— 设置返回值存根coEvery/coVerify— 用于 Kotlin 挂起函数(MockK)verify(mock).method(args)— 断言方法已被调用ArgumentCaptor— 捕获并检查参数spy()/spyk()— 对真实对象进行部分模拟- 数据替换优先使用 Fake;验证行为时使用 Mock
下一节:使用 Espresso 进行界面测试。
常见问题解答
「使用 Mockito 进行模拟」课时是免费的吗?
是的 — 「使用 Mockito 进行模拟」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Android Academy 课程的其余内容,请升级到 CoddyKit PRO。 Android Academy 课程共包含 4 节课。
「使用 Mockito 进行模拟」这节课中我会学到什么?
使用 Mockito 和 MockK 隔离依赖。设置返回值,验证交互,捕获参数,并使用间谍进行部分模拟。 你通过在浏览器中直接运行的动手代码来练习 Android Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Android Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Android Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 2 节课,共 4 节。
「使用 Mockito 进行模拟」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Android Academy 课中编写并运行代码吗?
能。每节 Android Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。
此课程中的所有课时
- 使用 JUnit 进行单元测试
- 使用 Mockito 进行模拟
- 使用 Espresso 进行 UI 测试
- 调试与性能分析