Mock入门
java的mock框架,其文档
参考
依赖的2种情况
- 一个类的方法依赖另一个类的方法
- 同一个类的方法间依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| public class A {
private B b;
public String a() { return b.b(); }
public String a1() { return a2(); }
public String a2() { return "a2"; } }
public class B { public String b() { return "this is b"; } }
|
Mock使用
引入依赖
1 2 3 4 5 6 7 8 9 10 11 12 13
| <!-- https://mvnrepository.com/artifact/org.mockito/mockito-core --> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <version>2.23.0</version> <scope>test</scope> </dependency>
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency>
|
需要注意的就是:springboot的spring-boot-starter-test
以及包含junit和mockito依赖。
如何模拟一个对象
原生方法
以List为例,使用mock方法即可:
1 2 3 4 5 6 7 8 9 10 11 12
| import static org.mockito.Mockito.*;
List mockedList = mock(List.class);
mockedList.add("one"); mockedList.clear();
verify(mockedList).add("one"); verify(mockedList).clear();
|
依赖情况1
或者使用注解:
文档 ,
@InjectMocks只会注入使用@Spy或@Mock注释创建的mock/spy对象
- 构造函数注入
- setter注入
- 字段注入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public class ATest {
@InjectMocks private A a;
@Mock private B b;
@Before public void init() throws Exception { MockitoAnnotations.initMocks(this); }
@Test public void a() { when(b.b()).thenReturn("bb"); final String s = this.a.a();
assertEquals("bb", s); } }
|
依赖情况2
这时候创建被测类的spy对象,才可以模拟其方法:
1 2 3 4 5 6 7 8
| @Test public void a1() { a = spy(a); when(a.a2()).thenReturn("aa");
assertEquals("aaa", a.a1()); }
|
JUnit+Mockito
当然,在junit测试框架下,我们可以完全使用注解:
1 2 3 4 5 6 7 8 9 10 11
| @RunWith(MockitoJUnitRunner.class) public class ATest {
@InjectMocks private A a;
@Mock private B b; }
|
模拟方法返回值
这是我们最常用的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| @Test(expected = RuntimeException.class) public void stub() { LinkedList mockedList = mock(LinkedList.class);
when(mockedList.get(0)).thenReturn("first"); when(mockedList.get(1)).thenThrow(new RuntimeException());
assertEquals("first", mockedList.get(0));
assertNull(mockedList.get(99));
mockedList.get(1); }
|
模拟方法参数
有时我们不关心参数的值,只需要随便给一个,mock可以随机一些:
1 2 3 4 5 6 7 8 9 10 11
| @Test public void arguments() { LinkedList mockedList = mock(LinkedList.class);
when(mockedList.get(anyInt())).thenReturn("element");
assertEquals("element", mockedList.get(3)); assertEquals("element", mockedList.get(999)); }
|
当然,除了anyInt(),还有其他类型:Stirng,Short,Char,List,Set,Map,Iterable,参考源码:org.mockito.ArgumentMatchers
.
或者我们自定义参数条件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| @Test public void arg() { class ListOfTwoElements implements ArgumentMatcher<List> { public boolean matches(List list) { return list.size() == 2; } public String toString() { return "[list of 2 elements]"; } }
List mock = mock(List.class);
when(mock.addAll(argThat(new ListOfTwoElements()))).thenReturn(true);
assertTrue(mock.addAll(Arrays.asList("one", "two"))); assertFalse(mock.addAll(Arrays.asList("one", "two", "three"))); }
|
验证方法的调用次数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| @Test public void timesTest() { LinkedList mockedList = mock(LinkedList.class);
mockedList.add("once");
mockedList.add("twice"); mockedList.add("twice");
mockedList.add("three times"); mockedList.add("three times"); mockedList.add("three times");
verify(mockedList).add("once"); verify(mockedList, times(1)).add("once");
verify(mockedList, times(2)).add("twice"); verify(mockedList, times(3)).add("three times");
verify(mockedList, never()).add("never happened");
verify(mockedList, atLeastOnce()).add("three times"); verify(mockedList, atLeast(2)).add("three times"); verify(mockedList, atMost(5)).add("three times"); }
|
好理解,但是为什么需要?
假如我们的被测方法存在一个循环,循环里有条件判断,可以判断某个方法因为条件的不同执行的次数。
doXxxx方法
文档
一般来说,when().thenReturn()
基本能满足需求,所以什么时候需要使用do系列方法?
- 模拟void的方法
- 模拟spy对象,上面
- 在一个测试中多次修改一个方法的行为
说一下多次修改一个方法的返回值:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| @Test public void getInstanceInfoActive() { when(openstackService.execute(anyString(), anyString(), anyString(), anyString(), anyString(), anyString(), argThat(s -> !s.contains("&marker=")))).thenReturn(getOneServerJson(2));
final int count = service.getInstanceInfoActive("", "", "", ""); assertEquals(2, count);
doReturn(getOneServerJson(1000)).when(openstackService).execute(anyString(), anyString(), anyString(), anyString(), anyString(), anyString(), argThat(s -> !s.contains("&marker="))); doReturn(getOneServerJson(999)).when(openstackService).execute(anyString(), anyString(), anyString(), anyString(), anyString(), anyString(), argThat(s -> s.contains("&marker=")));
final int count2 = service.getInstanceInfoActive("", "", "", ""); assertEquals(1999, count2); }
|
根据参数返回不同的值
https://stackoverflow.com/questions/22338536/mockito-return-value-based-on-property-of-a-parameter
使用doAnswer()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public void setUp() { doAnswer(new Answer<String>(){ @Override public String answer(InvocationOnMock invocation){ Obj yours = innovation.getArguments(0); theProperty = yours.getXXX(); if ("value".equals(theProperty)){ return "result"; } else if("otherValue".equals(theProperty)) { return "otherResult"; } return xxx; }}).when(mockObject).myMethod(anyString()); }
|
方法没有返回值
理论上应该抛出异常,否则就是方法本身不可测了。
方法是私有的
mockito框架不支持测试私有方法,原因
但有另一个强大的框架:powermock,可以测试私有方法。
修改权限:改为包权限。
其他
参考文档:
https://static.javadoc.io/org.mockito/mockito-core/2.23.0/org/mockito/Mockito.html
为什么使用Mock
Mockito是一个非常好的模拟框架。它允许您使用简洁的API编写漂亮的测试。 Mockito不会让你糊涂,因为测试非常易读并且会产生干净的验证错误。
StackOverflow社区将Mockito评为最佳的java模拟框架。尽管StackOverflow避开可能引发情绪争论的问题,但事实上Mockito获得的选票最多。
所有库中的前10个Java库,不仅仅是测试工具。 2013年底,对30,000个GitHub项目进行了分析。尽管Mockito在主报告中排名第9,但mockito-core和mockito-all都是相同的工具,因此Mockito的实际位置是4号,超过了Guava或Spring等著名工具。将这项研究视为Mockito每天用Java编写的单元测试所产生巨大影响的指标。
行为驱动开发的创始人Dan North在2008年写道:
“我们在主会议期间决定使用JUnit 4和Mockito,因为我们认为它们是TDD和Java中Mock的未来”
Spring中的Mock
spring-boot官方测试文档
spring-boot包含的测试依赖
mock在spring中的分类和使用
ScalaTest+Mock
官方文档:http://www.scalatest.org/user_guide/testing_with_mock_objects
scala的mock有4种常用框架:
scala-mock是scala原生支持的:http://scalamock.org/quick-start/