mock测试_mock对象2
此处使用Mockito示例
一、mockito简介
Mockito 是一种 Java mock 框架,用于创建和配置 mock 对象,主要是用来做 mock 测试的,它可以模拟任何 Spring 管理的 bean、模拟方法的返回值、模拟抛出异常...等,它同时也会记录调用这些模拟方法的参数、调用顺序,从而可以校验出这个 mock 对象是否有被正确的顺序调用,以及按照期望的参数被调用。
像是 Mockito 可以在单元测试中模拟一个 service 返回的数据,而不会真正去调用该 service,这就是上面提到的 mock 测试精神,也就是通过模拟一个假的 service 对象,来快速的测试当前我想要测试的类
目前在 Java 中主流的 mock 测试工具有 Mockito、JMock、EasyMock..等,而 SpringBoot 目前内建的是 Mockito 框架。
Mockito 是 GitHub 上使用最广泛的 Mock 框架,并与 JUnit 结合使用。使用 Mockito 简化了具有外部依赖的类的测试开发。

二、使用 Mockito 的步骤
-
模拟任何外部依赖并将这些模拟对象插入测试代码中
-
执行测试中的代码
-
验证代码是否按照预期执行
三、简单应用
1. 引入POM
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.10.19</version>
</dependency>
2. mock对象代码
public class SimpleTest { @Test public void test() { // 创建Mock对象,参数可以是类或者接口 List<String> list = mock(List.class); // 设置方法的预见期返回值 when(list.get(0)).thenReturn("zuozewei"); when(list.get(1)).thenThrow(new RuntimeException("test exception")); String result = list.get(0); // 验证方法调用 verify(list).get(0); // 断言,list的第一个元素是否是"zuozewei" Assert.assertEquals(result, "zuozewei"); } }
新建测试类,构造了 list 这样的对象,并且给一个元素赋值 zuozewei。在最后断言的时候,也可以断言这个 list 里面确实有这个值。所以,通过这种方式,我们可以进行对象构造。可以是类,也可以是接口。除了构造对象,当然也可以对方法设定的返回值指定异常。上述代码的意思就是当调用 list 的第二个元素的时候,抛出一个运行异常。
上面列举了 Mockito 的简单用法。
四、复杂应用
在 SpringBoot 单元测试中使用 Mockito
对于比较复杂的用法,可以通过官网深入学习。因为 Mockito 主要用于单元测试,开发人员用的比较多。
1. 首先在 pom.xml 下新增 spring-boot-starter-test 依赖,该依赖内就有包含了 JUnit、Mockito
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
2. 先写好一个 UserService,它里面有两个方法 getUserById() 和 insertUser(),而它们会分别再去调用 UserDao 这个 bean的 getUserById() 和 insertUser() 方法
@Component
public class UserService {
@Autowired
private UserDao userDao;
public User getUserById(Integer id) {
return userDao.getUserById(id);
}
public Integer insertUser(User user) {
return userDao.insertUser(user);
}
}
3. User model 的定义如下
public class User {
private Integer id;
private String name;
//省略 getter/setter
}
4. 如果这时候我们先不使用 Mockito 模拟一个假的 userDao bean,而是真的去调用一个正常的 Spring bean 的 userDao 的话,测试类写法如下。其实就是很普通的注入 userService bean,然后去调用它的方法,而它会再去调用 userDao 取得数据库的数据,然后我们再对返回结果做 assert 断言检查
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserServiceTest {
//先普通的注入一个userService bean
@Autowired
private UserService userService;
@Test
public void getUserById() throws Exception {
//普通的使用userService,他里面会再去调用userDao取得数据库的数据
User user = userService.getUserById(1);
//检查结果
Assert.assertNotNull(user);
Assert.assertEquals(user.getId(), new Integer(1));
Assert.assertEquals(user.getName(), "John");
}
}
5. 但是如果 userDao 还没写好,又想先测 userService 的话,就需要使用 Mockito 去模拟一个假的 userDao 出来
使用方法是在 userDao 上加上一个 @MockBean 注解,当 userDao 被加上这个注解之后,表示 Mockito 会帮我们创建一个假的 mock 对象,替换掉 Spring 中已存在的那个真实的 userDao bean,也就是说,注入进 userService 的 userDao bean,已经被我们替换成假的 mock 对象了,所以当我们再次调用 userService 的方法时,会去调用的实际上是 mock userDao bean 的方法,而不是真实的 userDao bean
Mockito库中创建模拟对象的三种方式:mock()方法、@Mock注解和@MockBean注解。
Mockito.mock()方法:允许我们在测试中指定方法行为并验证调用。
@Mock注解:用于模拟不属于Spring上下文的对象。实际上是Mockito.mock()方法的缩写,简化了mock对象的创建,但需配合MockitoJUnitRunner或手动初始化。
@MockBean:用于模拟属于Spring Boot应用程序中的Spring上下文的对象,将@Service、@Reporitory、@Component注释的实际beans替换为mock对象。与Spring集成测试,它可以将模拟对象注入到Spring上下文中,替代或新增实际bean。
@Mock和@InjectMocks的区别:
@Mock:创建一个模拟对象
@InjectMocks:创建一个该类的实例,并将使用@Mock注解创建的模拟对象注入到该实例中。@RunWith(MockitoJUnitRunner.class)
注意:必须使用@RunWith(MockitoJUnitRunner.class)或MockitoAnnotations.initMocks(this)进行mocks的初始化和注入。
当我们创建了一个假的 userDao 后,我们需要为这个 mock userDao 自定义方法的返回值,这里有一个公式用法,下面这段代码的意思为:当调用了某个 mock 对象的方法时,就回传给我们一个自定义结果。
Mockito.when( mock对象.方法名() ).thenReturn( 自定义结果 )
6. 使用 Mockito 模拟 bean 的单元测试具体实例如下
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserServiceTest {
@Autowired
private UserService userService;
@MockBean //创建mock对象
private UserDao userDao;
@Test
public void getUserById() throws Exception {
// 定义当调用mock userDao的getUserById()方法,并且参数为3时,就返回id为200、name为I'm mock3的user对象
Mockito.when(userDao.getUserById(3)).thenReturn(new User(200, "I'm mock 3"));
// 返回的会是名字为I'm mock 3的user对象,若此处入参传2,则返回的user为null
User user = userService.getUserById(3);
Assert.assertNotNull(user);
Assert.assertEquals(user.getId(), new Integer(200));
Assert.assertEquals(user.getName(), "I'm mock 3");
}
}
7. thenReturn 系列方法
当使用任何整数值调用 userDao 的 getUserById() 方法时,就回传一个名字为 I'm mock3 的 user 对象:
Mockito.when(userDao.getUserById(Mockito.anyInt())).thenReturn(new User(3, "I'm mock"));
User user1 = userService.getUserById(3); // 回传的user的名字为I'm mock
User user2 = userService.getUserById(200); // 回传的user的名字也为I'm mock
限制只有当参数的数字是 3 时,才会回传名字为 I'm mock 3 的 user 对象:
Mockito.when(userDao.getUserById(3)).thenReturn(new User(3, "I'm mock"));
User user1 = userService.getUserById(3); // 回传的user的名字为I'm mock
User user2 = userService.getUserById(200); // 回传的user为null
当调用 userDao 的 insertUser() 方法时,不管传进来的 user 是什么,都回传 100:
Mockito.when(userDao.insertUser(Mockito.any(User.class))).thenReturn(100);
Integer i = userService.insertUser(new User()); //会返回100
8. thenThrow 系列方法
当调用 userDao 的 getUserById() 时的参数是 9 时,抛出一个 RuntimeException:
Mockito.when(userDao.getUserById(9)).thenThrow(new RuntimeException("mock throw exception"));
User user = userService.getUserById(9); //会抛出一个RuntimeException
如果方法没有返回值的话(即是方法定义为public void myMethod() {...}),要改用 doThrow() 抛出 Exception:
Mockito.doThrow(new RuntimeException("mock throw exception")).when(userDao).print();
userService.print(); //会抛出一个RuntimeException
9. verify 系列方法
验证方法调用:
Mockito.verify(userDao).getUserById(Mockito.eq(3)) ;
Mockito.verify(userDao).getUserById(3);
验证调用次数,下文表示检查调用 userDao 的 getUserById()、且参数为3的次数是否为1次:
Mockito.verify(userDao, Mockito.times(1)).getUserById(Mockito.eq(3)) ;
验证调用顺序,验证 userDao 是否先调用 getUserById() 两次,并且第一次的参数是 3、第二次的参数是 5,然后才调用insertUser() 方法:
InOrder inOrder = Mockito.inOrder(userDao);
inOrder.verify(userDao).getUserById(3);
inOrder.verify(userDao).getUserById(5);
inOrder.verify(userDao).insertUser(Mockito.any(User.class));
五、Mockito常用参数匹配器
any(),anyInt(),anyString()- 匹配任意值eq()- 严格匹配isNull(),isNotNull()- 空值检查contains(),endsWith()- 字符串匹配argThat()- 自定义匹配逻辑
浙公网安备 33010602011771号