描述
上一篇文章写了几个简单MockMVC的测试类,可以简单了解下MockMVC的用法。这篇文章主要是用来介绍Mockito的用法。它可以通过模拟对象来执行你需要的测试行为。
Mock介绍
Mock,从字面上就知道是模拟的意思。其实它就是创建一个虚拟的对象,然后在测试环境中代替真实的对象,以达到最终的测试目的。借用官方通用的说法:
- 验证这个对象的某些方法的调用情况,调用了多少次,参数是什么等等
- 指定这个对象的某些方法的行为,返回特定的值,或者是执行特定的动作
如果有开发前端的同行们都知道,Mock.js是前后端分离必不可少的一门技术。
Mockito介绍
Mockito是Mock框架里应用最广泛的,也是java的一个应用。市面上还有Powermock for Java、GoogleMock for C++、Mock Server、WireMock、MockMVC。
Mockito的使用
-
依赖
<!-- SpringBoot框架的测试包,自动会引入这些Mock相关的包--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency>
<!-- 如果不是Spring framwork或纯java框架,要引入Junit包,且mockito-core --> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <scope>test</scope> </dependency>
-
创建Mock对象
-
使用静态的mock()方法
-
使用@Mock注解
注意的一点是,如果使用@Mock注解方法,你必须要触发@Mock注解对象的创建。
- 使用MockitoAnnotations.initMocks(this)方法触发所有的@Mock注解对象的创建
/** * MockitoTest * * @author orjrs * @date 2018-12-31 11:15 */ public class MockitoTest { @Mock private GirlValidateService girlValidateService; @Before // @Test注解的方法运行前执行,触发所有的@Mock注解对象的创建 public void setUp() { MockitoAnnotations.initMocks(this); } }
-
或者使用
@RunWith(MockitoJUnitRunner.class)
注解到类里/** * MockitoTest * * @author orjrs * @date 2018-12-31 11:15 */ @RunWith(MockitoJUnitRunner.class) // 可以自动触发所有的@Mock注解对象的创建 public class MockitoTest { @Mock private GirlValidateService girlValidateService; // @Before // @Test注解的方法运行前执行,触发所有的@Mock注解对象的创建 // public void setUp() { // MockitoAnnotations.initMocks(this); // } }
-
或者使用MockitoRule
/** * MockitoTest * * @author orjrs * @date 2018-12-31 11:15 */ // @RunWith(MockitoJUnitRunner.class) // 可以自动触发所有的@Mock注解对象的创建 public class MockitoTest { @Mock private GirlValidateService girlValidateService; @Rule // 可以自动触发所有的@Mock注解对象的创建 public MockitoRule mockitoRule = MockitoJUnit.rule(); // @Before // @Test注解的方法运行前执行,触发所有的@Mock注解对象的创建 // public void setUp() { // MockitoAnnotations.initMocks(this); // } }
-
-
完整的例子(GirlValidateServiceImpl和GirlServiceImpl后面再继续贴了):
package com.orjrs.spring.test.service.impl;
import com.orjrs.spring.test.model.Girl;
import com.orjrs.spring.test.service.GirlService;
import com.orjrs.spring.test.service.GirlValidateService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
/**
* GirlValidateServiceImpl
*
* @author orjrs
* @date 2018-12-31 11:40
*/
@Service("girlValidateService")
public class GirlValidateServiceImpl implements GirlValidateService {
private final GirlService girlService;
@Autowired
public GirlValidateServiceImpl(GirlService girlService) {
this.girlService = girlService;
}
@Override
public boolean validate(String name) {
if (StringUtils.isEmpty(name)) {
return false;
}
Girl girl = girlService.queryGirl(1L);
if (girl != null && name.equals(girl.getName())) {
return true;
}
return false;
}
}
package com.orjrs.spring.test.service.impl;
import com.orjrs.spring.test.model.Girl;
import com.orjrs.spring.test.service.GirlService;
import org.springframework.stereotype.Service;
/**
* GirlServiceImpl
*
* @author orjrs
* @date 2018-12-31 12:25
*/
@Service("girlService")
public class GirlServiceImpl implements GirlService {
@Override
public Girl queryGirl(Long id) {
Girl girl = new Girl();
girl.setId(1L);
girl.setName("张女士");
girl.setAge(23);
if (id.equals(girl.getId())) {
return girl;
}
return null;
}
}
/**
* GirlValidateServiceTest
*
* @author orjrs
* @date 2018-12-31 12:20
*/
public class GirlValidateServiceTest extends MockitoTest {// MockitoTest 参考上面其中任何一个
@Mock
private GirlService girlService;
@Test
public void validate() {
// 创建实例,mock对象作为构造函数的参数
GirlValidateService girlValidateService = new GirlValidateServiceImpl(girlService);
// 该方法体内调用GirlService的方法,此时@Mock出来的虚拟对象,是不会调用其实际方法
// girlService.queryGirl,所以返回的是null,导致判断为false
boolean flag = girlValidateService.validate("张女士");
assertFalse(flag);
Mockito.verify(girlService).queryGirl(1L); // 验证queryGirl调用是通过Mock对象的,这个方法的结果为null, girlService.queryGirl实际方法我已经写死了:1L 张女士 23
}
}
- 配置模拟
-
when thenReturn 和 when thenThrow
简单来说,当调用方法时,然后再返回自己设值或抛出自己设定异常
/** * GirlValidateServiceTest * * @author orjrs * @date 2018-12-31 12:20 */ public class GirlValidateServiceTest extends MockitoTest { @Mock private GirlService girlService; @Test public void validate() { // 创建实例,mock对象作为构造函数的参数 GirlValidateService girlValidateService = new GirlValidateServiceImpl(girlService); // 2. 配置mocks的测试:如果调用这个方法,始终返回一个自定义的对象 Mockito.when(girlService.queryGirl(1L)).thenReturn(new Girl() {{ setId(0L); setName("张女士"); }}); // girlService.queryGirl实际方法我已经写死了:1L 张女士 23 assertTrue(girlValidateService.validate("张女士")); // // 验证校验方法 } }
- doReturn when 和 doThrow when
它和when thenReturn 和when thenThrow很相似。它对于spy的用法来说是非常有用的。因为spy和mock的区别在于:前者会调用实际的方法,而mock不会。
请看下面关于spy的介绍,我会详细说明这个方法的作用。
package com.orjrs.spring.test.service;
import com.orjrs.spring.test.model.Girl;
import com.orjrs.spring.test.service.impl.GirlValidateServiceImpl;
import com.orjrs.spring.test.unit.MockitoTest;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.Spy;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
* GirlValidateServiceTest
*
* @author orjrs
* @date 2018-12-31 12:20
*/
public class GirlValidateServiceTest extends MockitoTest {
@Mock
private GirlService girlService;
@Spy // 如果不用@Spy注解,则可以Mockito.spy(spyGirlService)
private GirlService spyGirlService;
@Test
public void testSpyWrong() {
// mock 一个LinkedList
List<Girl> list = new LinkedList<>();
List<Girl> spy = Mockito.spy(list);
// 这个方法不会执行,因为spy的对象会去执行其实际方法,即spy.get(0)会去调用实际的方法,而list目前一个empty,则get(0)会异常
Mockito.when(spy.get(0)).thenReturn(new Girl() {{
setId(0L);
setName("李女士");
}});
// 上面一句会报错,这里不会执行
assertEquals("李女士", spy.get(0).getName());
}
@Test
public void testSpyCorrect() {
// mock 一个LinkedList
List<Girl> list = new LinkedList<>();
List<Girl> spy = Mockito.spy(list);
// 这个方法不会执行,因为spy的对象会去执行其实际方法,即spy.get(0)会去调用实际的方法,而list目前一个empty,则get(0)会异常
// 但doReturn when方法和when thenReturn不一样,.when(spy)并不会报错,调用get(0)它始终会返回一个结果
Mockito.doReturn(new Girl() {{
setId(0L);
setName("李女士");
}}).when(spy).get(0); // .when(spy).get(0);如果改成 when(spy.get(0)),一样会报错
// 会执行这行代码
assertEquals("李女士", spy.get(0).getName());
}
}
-
使用@InjectMocks依赖注入
@InjectMocks注解试图将构造函数,方法或字段通过基于类型来依赖注入。例如,假设你有下面的类。
注意:一定是基于类型,比如下面@InjectMocks // 创建一个实例,并注入@Mock的实例
private GirlValidateServiceImpl girlValidateServiceImpl;换成 GirlValidateServiceI接口就会报错package com.orjrs.spring.test.service; import com.orjrs.spring.test.model.Girl; import com.orjrs.spring.test.service.impl.GirlValidateServiceImpl; import com.orjrs.spring.test.unit.MockitoTest; import org.junit.Test; import org.mockito.*; import java.util.LinkedList; import java.util.List; import static org.junit.Assert.*; /** * GirlValidateServiceTest * * @author orjrs * @date 2018-12-31 12:20 */ public class GirlValidateServiceTest extends MockitoTest { @Mock private GirlService girlService; @Spy private GirlService spyGirlService; @InjectMocks // 创建一个实例,并注入@Mock的实例 private GirlValidateServiceImpl girlValidateServiceImpl; @Test public void validate() { // 创建实例,mock对象作为构造函数的参数 //GirlValidateService girlValidateService = new //GirlValidateServiceImpl(girlService); // 可以直接调用imGirlValidateServiceg,不需要上面的初始化 imGirlValidateServiceg.validate("张女士"); } }
-
局限性
Mockito不能模拟静态方法和私有方法。