- 前言
- Mockito BDDMockito
- mock
- stub
- matcher
- inorder
- consecutive
- answer
- spy
- oneLinerStubs
- 总结
向很多开源社区提交代码是需要做完整的 单元测试 的,其中 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 更加语义化
@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 ...
@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
@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 阶段下的所有行为
@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 风格的实现
@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
@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,旨在写出实用又好看的 单元测试
完整示例 demo



