Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
颯沓如流星
mockk
提交
e7e1b895
M
mockk
项目概览
颯沓如流星
/
mockk
通知
5
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
M
mockk
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
体验新版 GitCode,发现更多精彩内容 >>
提交
e7e1b895
编写于
10月 22, 2020
作者:
颯沓如流星
🎨
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
chore: README.md
上级
变更
1
显示空白变更内容
内联
并排
Showing
1 changed file
with
269 addition
and
0 deletion
+269
-0
README.md
README.md
+269
-0
未找到文件。
README.md
0 → 100644
浏览文件 @
e7e1b895
# 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.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录