关于 Mockito BDDMockito
前言
向很多开源社区提交代码是需要做完整的 单元测试
的,其中 mock
目标实例、给 mock
对象打桩 等操作可以极大提高测试代码的效率和可读性
本章节基于 Mockito
BDDMockito
的 API
DEMO
熟悉一些基本操作,Spring Test
包含对应依赖
Mockito BDDMockito
其中 BDD
代表 behavior driven deployment
,我理解就是让测试代码语义更接近现实行为,比如
given
就是给定 ... 条件下
,可以类比为打桩
(当然也可以不打)when
就是发生 ... 行为
,比如mock
实例的方法调用(当然也可以不发生)then
,就是则 ...
,打桩
的mock
实例执行目标方法后的校验
语义化的东西强行解释总感觉生硬
mock
@Test
public void mockDemo() {
List mock = mock(List.class);
mock.add("test");
mock.add("test1");
mock.add("test1");
mock.get(0);
// verify:是否调用目标方法
verify(mock).add("test");
// BDD style
then(mock).should().add("test");
// 目标方法调用一次
verify(mock, times(1)).add("test");
then(mock).should(times(1)).add("test");
// 目标方法至少调用 2 次
verify(mock, atLeast(2)).add("test1");
then(mock).should(atLeast(2)).add("test1");
// 目标方法最多调用 2 次
verify(mock, atMost(2)).add("test1");
then(mock).should(atMost(2)).add("test1");
// 目标方法未调用
verify(mock, never()).add("test2");
then(mock).should(never()).add("test2");
// error
verify(mock).add("test2");
verify(mock).get(1);
}
mock
方法创建mock 对象
,即一个临时虚拟对象,测试基于该示例进行verify
是Mockito
下的API
,类似于断言,比如verify(mock).add("test")
就是检验该方法是否调用then
是BDDMockito
的API
,类比于verify
更加语义化
stub
@Test
public void stubDemo() {
ArrayList mock = mock(ArrayList.class);
// 打桩
when(mock.get(0)).thenReturn("a");
// BDD-style
given(mock.get(0)).willReturn("a");
when(mock.get(1)).thenThrow(new RuntimeException("test"));
// given(mock.get(1)).willThrow(new RuntimeException("test"));
doThrow(new RuntimeException("void")).when(mock).clear();
// willThrow(new RuntimeException("void")).given(mock).clear();
System.out.println(mock.get(0));
try {
mock.get(1);
} catch (Exception e) {
System.out.println(e.getMessage());
}
System.out.println(mock.get(2));
}
- 这是一段
stub
代码,即打桩操作,主要应付方法逻辑复杂且非测试目标的场景 - 对应的
Mockito
API
是when ... then ...
- 对应的
BDDMockito
是then ... will ...
matcher
@Test
public void matcherDemo() {
ArrayList mock = mock(ArrayList.class);
// 匹配任意 int
when(mock.get(anyInt())).thenReturn("meta");
given(mock.get(anyInt())).willReturn("meta");
// 所有的方法都要使用 matcher
when(mock.subList(anyInt(), eq(1))).thenReturn(new ArrayList() {{
add("meta2");
}});
given(mock.subList(anyInt(), eq(1))).willReturn(new ArrayList() {{
add("meta2");
}});
System.out.println(mock.get(12));
System.out.println(mock.subList(3, 1));
// then 阶段也可以使用
verify(mock).get(anyInt());
then(mock).should().get(anyInt());
}
- 可以对参数进行匹配,比如
anyInt()
匹配任意Integer
- 这个针对
Mockito
BDDMockito
都一样 - 自然语义下的
then
阶段也可以使用matcher
来匹配
我指的自然语义就是 given ... when ... then ...
inorder
@Test
public void orderDemo() {
ArrayList mock = mock(ArrayList.class);
LinkedList mock2 = mock(LinkedList.class);
mock.add(1);
mock.add(2);
mock2.add(1);
mock2.add(2);
// 可以有多个实例
InOrder inOrder = inOrder(mock, mock2);
inOrder.verify(mock).add(1);
inOrder.verify(mock).add(2);
// correct,可以跳过
// inOrder.verify(mock2).add(2);
inOrder.verify(mock2).add(1);
// BDD-style
then(mock2).should(inOrder).add(2);
// error
inOrder.verify(mock).add(1);
}
- 这段代码可以测试
mock
实例的方法执行顺序是否符合预期 - 可以多个实例、多个方法结合使用,可看
demo
理解 - 顺序前后符合即可,可以不严格,即
1 2 3 4
可以是1 2 4
、1 3 4
但不可以2 1 3 4
consecutive
@Test
public void consecutiveDemo() {
ArrayList mock = mock(ArrayList.class);
when(mock.get(anyInt()))
.thenReturn(0, 1, 2);
// 0
System.out.println(mock.get(0));
// 1
System.out.println(mock.get(1));
// 2
System.out.println(mock.get(2));
// 2
System.out.println(mock.get(3));
// 清空
reset(mock);
System.out.println(mock.get(0));
}
打桩
的结果可以指定多个,会依此返回- 如果没有其他结果,则保持最后一个
reset
可情况mock
实例在自然语义given
和when
阶段下的所有行为
answer
@Test
public void answerDemo() {
ArrayList mock = mock(ArrayList.class);
when(mock.get(anyInt()))
.then(invocation -> {
Object argument = invocation.getArgument(0);
return argument + "r";
});
// BDD-style
given(mock.get(anyInt()))
.will(invocationOnMock -> {
Object argument = invocationOnMock.getArgument(0);
return argument + "r";
});
System.out.println(mock.get(0));
System.out.println(mock.get(1));
}
打桩
结果指定更加灵活的Answer
- 示例中是基于
lambda
风格的实现
spy
@Test
public void spyDemo() {
List list = new LinkedList();
List spy = spy(list);
spy.add(1);
when(spy.size()).thenReturn(10);
// BDD-style
given(spy.size()).willReturn(10);
System.out.println(spy.get(0));
System.out.println(spy.size());
// spy 只是一个 copy,所以 list 上的操作不影响 spy
list.add(2);
spy.forEach(System.out::println);
}
spy
方法可返回一个spy
实例,该实例除了打桩
行为外的调用都与原实例相同,比如示例中spy.add(1)
方法就是正常的ArrayList::add
,而spy.size()
因为被stub
只返回10
- 值得注意的是,原对象上的操作并不影响
spy
示例,比如示例中list.add(2)
并不意味着spy.get(1) == 2
oneLinerStubs
@Test
public void oneLinerStubsDemo() {
HelloService helloService = when(mock(HelloService.class).hello())
.thenReturn("hello world")
.getMock();
// BDD-style
HelloService helloService2 = given(mock(HelloService.class).hello())
.willReturn("hello world 2")
.getMock();
System.out.println(helloService.hello());
System.out.println(helloService2.hello());
}
- 一行式
demo
getMock
方法返回mock
实例
总结
本文介绍一些基于 Mockito
BDDMockito
的 DEMO
,旨在写出实用又好看的 单元测试