Mockito
介绍
Mockito 是 Java 单元测试的 Mock 框架,使用 Mock 技术能让单元测试隔离外部依赖,可以避免繁琐的初始化工作,让我们更专注于业务逻辑的测试。
Mockito 的工作原理
通过创建依赖对象的 proxy,所有的调用优先经过 proxy 对象,proxy 对象会拦截所有请求,并返回值我们预置的值进行处理。
Mockito 的包引入
-
gradle
// https://mvnrepository.com/artifact/org.mockito/mockito-core testCompile group: 'org.mockito', name: 'mockito-core', version: '3.5.11'
-
maven
<!-- https://mvnrepository.com/artifact/org.mockito/mockito-core --> <dependency><groupId>org.mockito</groupId><artifactId>mockito-core</artifactId><version>3.5.11</version><scope>test</scope> </dependency>
使用
Mockito 可以通过创建 mock 和 spy 对象,指定返回值来实现模拟的功能。当调用结束时,Mockito 框架提供了验证方法用来检验调用过程的正确性。
1. mock 一个 list
Mockito 提供了静态方法 mock 来模拟创建对象,也可以通过注解 @Mock 创建。
模拟的 List 对象可以调用 List 的方法,verify 验证了调用过程,例如 mock.clear() 没被调用,verify(mock).clear() 则会报错。
- mock 方法创建模拟对象
@Test
public void testMock() {List mock = Mockito.mock(List.class);mock.add("Hello Mockito");mock.clear();verify(mock).add("Hello Mockito");verify(mock).clear();
}
- @Mock 注解创建模拟对象
public class MockitoTest {@Mockprivate List mockList;@Beforeprivate void testMockBefore() {MockitoAnnotations.initMocks(this)}@Testpublic void testMock() {mockList.add("Hello Mockito");mockList.clear();verify(mockList).add("Hello Mockito");verify(mockList).clear();}
}
- @Mock 注解创建模拟对象
@RunWith(MockitoJUnitRunner.class)
public class MockitoTest {@Mockprivate List mockList;@Testpublic void testMock() {mockList.add("Hello Mockito");mockList.clear();verify(mockList).add("Hello Mockito");verify(mockList).clear();}
}
2. mock 的 list 对象设置返回值
-
when - thenReturn 设置返回值
List mock = mock(List.class); when(mock.size()).thenReturn(1); assertEquals(1, mock.size());
-
doReturn-when 设置返回值
List mock = mock(List.class); doReturn(1).when(mock).size(); assertEquals(1, mock.size());
3. mock 异常
-
when - thenThrow 模拟异常
@Test(expected = NullPointerException.class) public void testWhenThenThrow() { List mock = mock(List.class);when(mock.get(anyInt())).thenThrow(new NullPointerException("NullPointer"));mock.get(0); }
-
doThrow - when 模拟异常
@Test(expected = NullPointerException.class) public void testDoThrowWhen() throws IOException { List mock = mock(List.class);doThrow(new NullPointerException("NullPointer")).when(mock).get(anyInt());mock.get(0); }
4. mock 设置的连续调用
当设置 mock 的返回值时,如果不连续调用,仅仅是连续设置,则会已最后一次设置为准
@Test
public void testConsecutiveCalls() {List mock = mock(List.class);when(mock.get(0)).thenReturn(0);when(mock.get(0)).thenReturn(1);when(mock.get(0)).thenReturn(2);Assert.assertEquals(2, mock.get(0));
}
连续调用的设置返回值 when().thenReturn().thenReturn()…
@Test(expected = RuntimeException.class)
public void testConsecutiveCalls() {
List mock = mock(List.class);when(mock.get(0)).thenReturn(100).thenThrow(new RuntimeException());mock.get(0);mock.get(0);
}
第一次调用 mock.get(0) 返回 100,第二次调用 mock.get(0),发生 RuntimeException 异常
5. 验证执行顺序
可以通过 InOrder 验证 mock 的执行顺序
@Test
public void testVerifyOrder() {
List list1 = mock(List.class);List list2 = mock(List.class);list1.add(1);list2.add("Hello");list1.add(2);list2.add(" Mockito");InOrder inOrder = inOrder(list1, list2);inOrder.verify(list1).add(1);inOrder.verify(list2).add("Hello");inOrder.verify(list1).add(2);inOrder.verify(list2).add(" Mockito");
}
上述例子中,验证 list1, list2 的操作顺序,如果操作变化,则会验证失败。
6. 模拟对象的互动
-
验证没有任何 Interactions
@Test public void testVerifyInteraction() { List list1 = mock(List.class);List list2 = mock(List.class);List list3 = mock(List.class);list1.add(1);// list2, list3 无互动verifyZeroInteractions(list2, list3); }
如果 list2,list3 有方法调用,则 verifyZeroInteractions 会失败。
-
查找冗余的 Interactions
@Test public void testVerifyNoMoreInteraction() { List list1 = mock(List.class);list1.add(1);list1.add(2);verify(list1, times(2)).add(anyInt());verifyNoMoreInteractions(list1);List list2 = mock(List.class);list2.add(1);list2.add(2);verify(list2).add(1);verifyNoMoreInteractions(list2); }
list1 verify 已经验证两次 add 操作,再次验证无冗余操作则通过;list2 未对 add(2) 进行验证,再次验证无冗余操作则失败。
7. Spy 包装 list 对象
Mock 一个对象并不是真实的对象,但是可以模拟对象行为;Spy 一个对象是真实对象,同时可以模拟对象行为。
@Test
public void testSpyOnRealObjects() {
List list = new LinkedList();List spy = spy(list);// 真实的 list addspy.add(100);verify(spy).add(100);assertEquals(100, spy.get(0));
}
如果是 mock 的对象,List.get(0) 将返回 null。
8. @InjectMocks 注入对象
-
注入 mock 对象
class AddressMap { Map<String, String> addressMap;public AddressMap() { addressMap = new HashMap<String, String>();}public void add(final String number, final String address) { addressMap.put(number, address);}public String get(final String number) { return addressMap.get(number);} }@Mock Map<String, String> addressMap;@InjectMocks AddressMap addresses = new AddressMap();@Test public void testInjectMocks() { when(addressMap.get("001")).thenReturn("Beijing");Assert.assertEquals("Beijing", addresses.get("001")); }
mock 对象 addressMap 注入 AddressMap 对象 addresses,测试通过。
-
注入 spy 对象
class AddressMap { Map<String, String> addressMap;public AddressMap(Map<String, String> map) { addressMap = map;}public void add(final String number, final String address) { addressMap.put(number, address);}public String get(final String number) { return addressMap.get(number);} }@Mock Map<String, String> addressMap;AddressMap addresses;@Before public void testInjectMocksBefore() { MockitoAnnotations.initMocks(this);// 初始化addresses = Mockito.spy(new AddressMap(addressMap)); }@Test public void testInjectMocks() { when(addressMap.get("001")).thenReturn("Beijing");Assert.assertEquals("Beijing", addresses.get("001")); }
spy 对象注入需要注意初始化,spy 对象是真实对象,没有初始化的对象会抛出异常。
9. ArgumentCaptor 捕获方法参数
class Person {private int id;private String name;Person(int id, String name) {this.id = id;this.name = name;}public int getId() {return id;}public String getName() {return name;}
}
interface PersonDao {public void update(Person person);
}class PersonService {private PersonDao personDao;PersonService(PersonDao dao) {this.personDao = dao;}public void update(int id, String name) {personDao.update(new Person(id, name));}
}
@Test
public void testArgumentCaptor() {PersonDao personDao = mock(PersonDao.class);PersonService service = new PersonService(personDao);ArgumentCaptor<Person> captor = ArgumentCaptor.forClass(Person.class);service.update(1, "jack");verify(personDao).update(captor.capture());Assert.assertEquals(1, captor.getValue().getId());Assert.assertEquals("jack", captor.getValue().getName());
}
ArgumentCaptor 捕获了 update 的参数,用于 mock 方法 getId(),getName() 的参数校验。
10. 重置 mock 对象
List list = mock(List.class);
...
Mockito.reset(xxx);
...
mock 对象重置后恢复初始化状态。