Mock 测试就是在测试过程中,对于某些不容易构造(如 HttpServletRequest 必须在Servlet 容器中才能构造出来)或者不容易获取比较复杂的对象(如 JDBC 中的ResultSet 对象),用一个虚拟的对象(Mock 对象)来创建以便测试的测试方法。
2. Mock测试的常规步骤- Mock:创建出待测试的Mock对象
- Stubbing:指定它的行为,return特定值或者抛出特定异常。when().thenReturn()或when().thenThrow()等
- Verify:验证结果
很多文章把Stubbing翻译成“打桩”或者“存根”,其实这样的翻译只会让人费解,所以不翻译,直接叫Stubbing,对程序员来说更好理解。
Mockito是比较出名且方便的Mock工具,它的API都比较直观,易于理解。
如果普通Java项目,不依赖SpringBoot,则直接引入
org.mockito mockito-all 3.9.0 test
如果是SpringBoot项目
三、Mockito实践 0. 前提准备:待测试的业务代码org.springframework.boot spring-boot-starter-test test
先创建几个非常简单的文件,这个结构大伙一看就懂。
DictTypeServiceImpl 的具体实现如下:
可以看到DictTypeServiceImpl中是依赖DictTypeDao的。
@Service public class DictTypeServiceImpl extends CommonServiceImpl1. 第一步:Mockimplements DictTypeService { //这里依赖了DictTypeDao @Autowired private DictTypeDao dictTypeDao; public DictType save(DictType dictType) { return dictTypeDao.save(dictType); } public void deleteById(String id){ dictTypeDao.deleteById(id); } public Optional getById(String id) { return dictTypeDao.findById(id); } public Iterable listByQuery(QueryBuilder queryBuilder) { return search(queryBuilder, DictType.class); } public boolean existById(String id) { return dictTypeDao.existsById(id); } }
举例说明:
此时我就想写UT测试Service层的代码,不测试Dao层的代码,那就需要虚拟一个Dao层的对象(Mock对象)给Service用。
比如模拟Dao层中getById(“1”)的返回值,指定返回一个我们new的对象,而不是让Dao去查询数据库。
代码如下:
//我们自己定义一个返回值
DictType dictType = new DictType();
dictType.setId("1");
dictType.setCode("code1");
dictType.setName("name1");
Optional dictTypeOptional = Optional.of(dictType);
//Stubbing,指定当执行到findById("1")时,返回上面定义的值,而不是执行真正的代码去查数据库
when(dictTypeDao.findById("1")).thenReturn(dictTypeOptional);
写这个UT时,可以创建3种对象:被测试对象、Mock对象、Spy对象。
- 被测试对象:就是你要测试的类,本博客中指DictTypeServiceImpl的对象。
- Mock对象:虚拟出的对象,被测试对象依赖Mock对象。因为DictTypeServiceImpl中依赖DictTypeDao。本博客中Mock对象指DictTypeDao的对象。
- Spy对象:Spy对象是一种特殊的Mock对象,它也可以作为被测对象的依赖对象。此时它和Mock对象的最大的区别:Mock对象的方法如果没有被Stubbing,调用时会返回相应对象的空值,而Spy对象的方法被调用时则会调用真实的代码逻辑。
@InjectMocks的作用和@Autowired比较类似,但是它的成员变量将被@Mock和@Spy注解的字段注入。
DictTypeServiceImpl 的定义如下:
@Service public class DictTypeServiceImpl extends CommonServiceImplimplements DictTypeService { //这里依赖了DictTypeDao @Autowired private DictTypeDao dictTypeDao; //省略其他方法 }
测试类中用@InjectMocks修饰在DictTypeServiceImpl上,那么@Mock或者@Spy修饰的对象会注入到@InjectMocks修饰的对象里。
注意@InjectMocks修饰在实现类上,而不是DictTypeService接口层,这个和@Autowired有不同。
被测试的DictTypeServiceImpl中代码
public OptionalgetById(String id) { return dictTypeDao.findById(id); }
用@Mock修饰在对象上,就可以实现Mock对象。
@SpringBootTest
public class MockitoWebTest {
@InjectMocks
DictTypeServiceImpl dictTypeService;
@Mock
private DictTypeDao dictTypeDao;
@Test
public void testMockObject() {
//我们自己定义一个返回值
DictType dictType = new DictType();
dictType.setId("1");
dictType.setCode("mock_code1");
dictType.setName("mock_name1");
Optional dictTypeOptional = Optional.of(dictType);
//Stubbing,指定当执行到findById("1")时,返回上面定义的值
when(dictTypeDao.findById("1")).thenReturn(dictTypeOptional);
Optional dictTypeById_1 = dictTypeService.getById("1");
Optional dictTypeById_2 = dictTypeService.getById("2");
System.out.println(dictTypeById_1.get().getCode());
if (dictTypeById_2.isEmpty()){
System.out.println("dictTypeById_2为空");
} else {
System.out.println(dictTypeById_2.get().getCode());
}
}
}
运行结果:
mock_code1 dictTypeById_2为空
- Stubbing了dictTypeDao.findById(“1”),所以返回了我们自己预定义的值
- 没有Stubbing dictTypeDao.findById(“2”),所以返回了空
when(dictTypeDao.findById("1")).thenReturn(dictTypeOptional);
2.2 有返回的值的方法,模拟抛出异常
when(dictTypeDao.findById("1")).thenThrow(new RuntimeException());
2.3 无返回值void方法,模拟抛出异常
doThrow(new RuntimeException()).when(dictTypeDao).deleteById("1");
2.4 doNothing
doNothing().when(dictTypeRepository).deleteById("1");
2.5 匹配参数
上面的都是指定参数的,如果要匹配任意参数可以用anyString()、anyInt()、any(Class type)等等。
doThrow(new RuntimeException()).when(dictTypeRepository).deleteById(anyString());3. 第三步:Verify
verify()是验证方法执行的次数。
when(mockedList.get(anyInt())).thenReturn("element");
System.out.println(mockedList.get(999));
System.out.println(mockedList.get(99));
verify(mockedList, times(1)).get(anyInt());
verify(mockedList).get(anyInt());//1次的也可以简写成这样
verify(mockedList, times(2)).get(anyInt());
verify(mockedList, atMost(2)).get(anyInt());
verify(mockedList, atLeast(2)).get(anyInt());
verify(mockedList, atLeastOnce()).get(anyInt());
verify(mockedList, never()).get(9);



