在软件开发中提及”mock”,通常理解为模拟对象。
为什么需要模拟? 在我们一开始学编程时,我们所写的对象通常都是独立的,并不依赖其他的类,也不会操作别的类。但实际上,软件中是充满依赖关系的,比如我们会基于service类写操作类,而service类又是基于数据访问类(DAO)的,依次下去,形成复杂的依赖关系。
单元测试的思路就是我们想在不涉及依赖关系的情况下测试代码。这种测试可以让你无视代码的依赖关系去测试代码的有效性。核心思想就是如果代码按设计正常工作,并且依赖关系也正常,那么他们应该会同时工作正常。
mock技术的目的和作用就是模拟一些在应用中不容易构造或者比较复杂的对象,从而把测试与测试边界以外的对象隔离开。
Mockito就是一个优秀的用于单元测试的mock框架
除了Mockito以外,还有一些类似的框架
- EasyMock:早期比较流行的MocK测试框架。它提供对接口的模拟,能够通过录制、回放、检查三步来完成大体的测试过程,可以验证方法的调用种类、次数、顺序,可以令 Mock 对象返回指定的值或抛出指定异常
- PowerMock:这个工具是在EasyMock和Mockito上扩展出来的,目的是为了解决EasyMock和Mockito不能解决的问题,比如对static, final, private方法均不能mock。其实测试架构设计良好的代码,一般并不需要这些功能,但如果是在已有项目上增加单元测试,老代码有问题且不能改时,就不得不使用这些功能了
- JMockit:JMockit 是一个轻量级的mock框架是用以帮助开发人员编写测试程序的一组工具和API,该项目完全基于 Java 5 SE 的 java.lang.instrument 包开发,内部使用 ASM 库来修改Java的Bytecode
引入 jar 包依赖
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>1.10.19</version>
<scope>test</scope>
</dependency>
dependencies {
testCompile 'org.mockito:mockito-core:1.10.19'
}
入门案例
import static org.mockito.Mockito.*;
public class AnswerApp {
@Test
@SuppressWarnings("unchecked")
public void test() {
// 创建Mock对象,参数可以是类或者接口
List<String> list = mock(List.class);
// 设置方法的预期返回值
when(list.get(0)).thenReturn("AnswerAIL");
when(list.get(1)).thenThrow(new RuntimeException("test exception"));
String result = list.get(0);
// 验证方法调用
verify(list).get(0);
// 断言,list的第一个元素是否是"AnswerAIL"
Assert.assertEquals(result, "AnswerAIL");
}
}
模拟 Mapper 层数据库返回
@Data
public class User {
private Long id;
private String name;
public User(Long id, String name) {
this.id = id;
this.name = name;
}
}
public class UserService {
private final UserDao userDao;
public UserService(UserDao userDao) {
this.userDao = userDao;
}
public boolean update(int id, String name) {
User user = userDao.findUser(id);
if (user == null) {
return false;
}
User personUpdate = new User(user.getId(), name);
return userDao.updateUser(personUpdate);
}
}
public interface UserDao {
User findUser(int id);
boolean updateUser(User user);
}
public class UserServiceTest {
private UserDao mockDao;
private UserService userService;
@Before
public void setUp() throws Exception {
// 模拟 UserDao 对象
mockDao = mock(UserDao.class);
// 模拟 Dao 层的数据返回
when(mockDao.findUser(1)).thenReturn(new User(1L, "Answer"));
when(mockDao.updateUser(isA(User.class))).thenReturn(true);
userService = new UserService(mockDao);
}
@Test
public void testUpdate() throws Exception {
boolean result = userService.update(1, "Iris");
assertTrue("must true", result);
// 验证是否执行过一次 findUser(1)
verify(mockDao, times(1)).findUser(eq(1));
// 验证是否执行过一次 updateUser
verify(mockDao, times(1)).updateUser(isA(User.class));
}
@Test
public void testUpdateNotFind() throws Exception {
boolean result = userService.update(2, "Iris");
assertFalse("must true", result);
// 验证是否执行过一次 findUser(1)
verify(mockDao, times(1)).findUser(eq(1));
// 验证是否执行过一次 updateUser
verify(mockDao, never()).updateUser(isA(User.class));
}
}
Reference
- mockito 源码
- Mockito使用指南
- Mockito Api
- JsonPath 语法说明
- MockMvc 模拟对 controller 进行单元测试 源码
- Java单元测试技巧之PowerMock
- 收藏!Java编程技巧之单元测试用例编写流程