提交 e7e1b895 编写于 作者: 颯沓如流星's avatar 颯沓如流星 🎨

chore: README.md

上级
# Mock
## Mockito
`Mockito` 是一个用 Java 写的 Mocking(模拟)框架
### Mockito 存在的问题
- 类型: Mockito 不支持对 final class、匿名内部类以及基本类型(如 int)的 mock。
- 方法: Mockito 不支持对静态方法、 final 方法、私有方法、equals() 和 hashCode() 方法进行 mock。
在 Kotlin 写的测试中用 Mockito 会用得很不顺手,之所以不顺手有两点。
- 第一点是上面说到的 Mockito 不支持对 final 类和 final 方法进行 mock,而在 Kotlin 中类和方法默认都是 final 的,也就是当你使用 Mockito 模拟 Kotlin 的类和方法时,你要为它们加上 open 关键字,如果你的项目中的类都是用 Kotlin 写的,那这一点会让你非常头疼。
- 第二点是 Mockito 的 when 方法与 Kotlin 的关键字冲突了,当 when 的数量比较多时,写出来的代码看上去会比较别扭,比如下面这样的。
```java
@Test
fun testAdd() {
`when`(calculator!!.add(1, 1)).thenReturn(2)
assertEquals(calculator!!.add(1, 1), 2)
}
```
`MockK` 是一个用 Kotlin 写的 Mocking 框架,它解决了所有上述提到的 Mockito 中存在的问题。
## MockK
### 快速入门
使用 MockK 测试 Calculator
```kotlin
@Test
fun testAdd() {
// 每一次 add(1, 1) 被调用,都返回 2
// 相当于是 Mockito 中的 when(…).thenReturns(…)
every { calculator.add(1, 1) } returns 2
assertEquals(calculator.add(1, 1), 2)
}
```
```kotlin
class GoodsPresenterTest {
private var presenter: GoodsPresenter? = null
// @MockK(relaxed = true)
@RelaxedMockK
lateinit var view: GoodsContract.View
@Before
fun setUp() {
MockKAnnotations.init(this)
presenter = GoodsPresenter()
presenter!!.attachView(view)
}
@Test
fun testGetGoods() {
val goods = presenter!!.getGoods(1)
assertEquals(goods.name, "纸巾")
}
}
```
在 MockK 中,如果你模拟的对象的方法是`没有返回值`的,并且你也不想要指定该方法的行为,你可以指定 `relaxed = true` ,也可以使用 `@RelaxedMockK` 注解,这样 MockK 就会为它指定一个默认行为,否则的话会报 MockKException 异常。
### 为无返回值的方法分配默认行为
`every {…}` 后面的 Returns 换成 `just Runs` ,就可以让 MockK 为这个**没有返回值的方法分配一个默认行为**
```kotlin
@Test
fun testGetGoods() {
val goods = presenter!!.getGoods(1)
every { view.showLoading() } just Runs
verify { view.showLoading() }
assertEquals(goods.name, "纸巾")
}
```
### 为所有模拟对象的方法分配默认行为
如果测试中有多个模拟对象,且你想为它们的`全部方法`都分配默认行为,那你可以在初始化 MockK 的时候指定 relaxed 为 true,比如下面这样。
```kotlin
@Before
fun setUp() {
MockKAnnotations.init(this, relaxed = true)
}
```
使用这种方式我们就`不需要`使用 `@RelaxedMockK` 注解了,直接使用 `@MockK` 注解即可。
### 验证多个方法被调用
在 GoodsPresenter 的 getGoods() 方法中调用了 View 的 showLoading() 和 hideLoading() 方法,如果我们想验证这两个方法执行了的话,我们可以把两个方法都放在 verify {…} 中进行验证。
```kotlin
@Test
fun testGetGoods() {
val goods = presenter!!.getGoods(1)
verify {
view.hideLoading()
view.showLoading()
}
assertEquals(goods.name, "纸巾")
}
```
### 验证方法被调用的次数
如果你不仅想验证方法被调用,而且想验证该方法被`调用的次数`,你可以在 `verify` 中指定 `exatcly``atLeast``atMost` 属性,比如下面这样的。
```kotlin
@Test
fun testGetGoods() {
val goods = presenter!!.getGoods(1)
// 验证调用了两次
verify(exactly = 2) { view.showToast("请耐心等待") }
// 验证调用了最少一次
// verify(atLeast = 1) { view.showToast("请耐心等待") }
// 验证最多调用了两次
// verify(atMost = 1) { view.showToast("请耐心等待") }
assertEquals(goods.name, "纸巾")
}
```
之所把 atLeast 和 atMost 注释掉,是因为这种类型的验证只能进行其中一种,而不能多种同时验证。
### 验证 Mock 方法都被调用了
Mock 方法指的是,我们当前调用的方法中,调用了的模拟对象的方法。
```kotlin
@Test
fun testGetGoods() {
val goods = presenter!!.getGoods(1)
verifyAll {
view.showToast("请耐心等待")
view.showToast("请耐心等待")
view.showLoading()
view.hideLoading()
}
assertEquals(goods.name, "纸巾")
}
```
### 验证 Mock 方法的调用顺序
```kotlin
@Test
fun testGetGoods() {
val goods = presenter!!.getGoods(1)
verifyOrder {
view.showLoading()
view.hideLoading()
}
assertEquals(goods.name, "纸巾")
}
```
### 验证全部的 Mock 方法都按特定顺序被调用了
如果你不仅想测试好几个方法被调用了,而且想确保它们是按`固定顺序`被调用的,你可以使用 `verifySequence {…}` ,比如下面这样的。
```kotlin
@Test
fun testGetGoods() {
val goods = presenter!!.getGoods(1)
verifySequence {
view.showLoading()
view.showToast("请耐心等待")
view.showToast("请耐心等待")
view.hideLoading()
}
assertEquals(goods.name, "纸巾")
}
```
### 确认所有 Mock 方法都进行了验证
把我们的模拟对象传入 `confirmVerified()` 方法中,就可以确认**是否验证了模拟对象的每一个方法**
```kotlin
@Test
fun testGetGoods() {
val goods = presenter!!.getGoods(1)
verify {
view.showLoading()
view.showToast("请耐心等待")
view.showToast("请耐心等待")
view.hideLoading()
}
confirmVerified(view)
assertEquals(goods.name, "纸巾")
}
```
### 验证 Mock 方法接收到的单个参数
如果我们想验证方法接收到的参数是`预期的参数`,那我们可以用 `capture(slot)` 进行验证,比如下面这样的。
```kotlin
@Test
fun testCaptureSlot() {
val slot = slot<String>()
every { view.showToast(capture(slot)) } returns Unit
val goods = presenter!!.getGoods(1)
assertEquals(slot.captured, "请耐心等待")
}
```
### 验证 Mock 方法每一次被调用接收到参数
如果一个方法被调用了多次,可以使用 `capture(mutableList)` 将每一次被调用时获取到的参数记录下来, 并在后面进行验证,比如下面这样。
```kotlin
@Test
fun testCaptureList() {
val list = mutableListOf<String>()
every { view.showToast(capture(list)) } returns Unit
val goods1 = presenter!!.getGoods(1)
assertEquals(list[0], "请耐心等待")
assertEquals(list[1], "请耐心等待")
}
```
### 验证使用 Kotlin 协程进行耗时操作
使用 Mockito 测试异步代码,只能通过 Thread.sleep()
阻塞当前线程,否则异步任务还没完成,当前测试就完成了,当前测试所对应的线程也就结束了,没有线程能处理回调中的结果。
当我们的协程涉及到线程切换时,我们需要在 setUp() 和 tearDown() 方法中设置和重置主线程的代理对象。
使用 `verify(timeout) {…}` 就可以实现延迟验证,比如下面代码中的 timeout = 2000 就表示在 2 秒后检查该方法是否被调用。
```kotlin
class GoodsPresenterTest {
private val mainThreadSurrogate = newSingleThreadContext("UI Thread")
private var presenter: GoodsPresenter? = null
@MockK
lateinit var view: GoodsContract.View
@Before
fun setUp() {
MockKAnnotations.init(this, relaxed = true)
presenter = GoodsPresenter()
presenter!!.attachView(view)
Dispatchers.setMain(mainThreadSurrogate)
}
@After
fun tearDown() {
Dispatchers.resetMain()
mainThreadSurrogate.close()
}
@Test
fun testBlockingTask() {
presenter!!.requestGoods(1)
verify(timeout = 2000) { view.hideLoading() }
}
}
```
### 添加依赖
```kotlin
// Unit tests
testImplementation "io.mockk:mockk:1.9.3"
// Instrumented tests
androidTestImplementation('io.mockk:mockk-android:1.9.3') { exclude module: 'objenesis' }
androidTestImplementation 'org.objenesis:objenesis:2.6'
// Coroutine tests
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.0-M2'
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.3.0-M2'
```
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册