0Pricing
Android Academy · 课时

使用 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 call

verify() — 检查交互

使用 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() — 根据接口或类创建 Mock
  • whenever(...).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 反馈 — 无需本地设置。

此课程中的所有课时

  1. 使用 JUnit 进行单元测试
  2. 使用 Mockito 进行模拟
  3. 使用 Espresso 进行 UI 测试
  4. 调试与性能分析
← 返回 Android Academy