Mockito 是一个模拟测试框架,主要功能是在单元测试中模拟类/对象的行为。
1 为什么要使用Mockito?
Mock可以理解为创建一个虚假的对象,或者说模拟出一个对象.在测试环境中用来替换掉真实的对象,以达到我们可以
验证该对象的某些方法的调用情况,调用了多少次,参数是多少.
给这个对象的行为做一个定义,来指定返回结果或指定特定的动作.
2 Mockito数据隔离
根据 JUnit 单测隔离 ,当 Mockito 和 JUnit 配合使用时,也会将非static变量或者非单例隔离开。
比如使用 @Mock 修饰的 mock 对象在不同的单测中会被隔离开。
3 Mock方法
3.1 mock 对象:创建模拟对象
3.1.1 mock 实例对象/实例方法: mock(Class classToMock)
mock方法来自org.mockito.Mock
,它标识可以mock一个对象或者是接口
public static <T> mock(Class<T> classToMock);
入参:classToMock: 待mock对象的class类
返回:mock出来的类的对象(此时对象内依旧无数据)
Random random = Mockito.mock(Random.class);
when(random.nextBoolean()).thenReturn(true);
logger.info("result: {}", random.nextBoolean());//result: true
3.1.2 mock 类对象/静态方法: mockStatic(T.class)
注:
QueryFactory#query
为静态方法
/**
* 静态类 T、静态方法的 Mock
* 1、mock方式: MockedStatic<T> mockClass = mockStatic(T.class); mockClass.when(...).thenReturn(...)
* 2、静态类/静态方法需在多个测试用例中 mock 时,需使用: `try-with-resources`的方式,以解决此问题"To create a new mock, the existing static mock registration must be deregistered"
*/
try(MockedStatic<QueryFactory> mockedQueryFactory = Mockito.mockStatic(QueryFactory.class)){
mockedQueryFactory.when( () -> QueryFactory.query(any(), any(), any(), any()) ).thenReturn(result);
signalNamesMappingCacheService.init();
try {
commonSearchBizSevice.executeQuery(requestInfo);//内部将调用 QueryFactory.query(...)
} catch (Exception e) {
logger.info("happens a exception: SUCCESS");//内置脚本反射调用失败
}
}
3.2 Stub 存根 : 为mock对象规定预期的目标执行结果
简述
存根的意思就是给mock对象规定一行的行为,使其按照我们的要求来执行具体的动作。
样例程序
demo1
//You can mock concrete classes, not just interfaces
LinkedList mockedList = mock(LinkedList.class);
//stubbing
when(mockedList.get(0)).thenReturn("first");
when(mockedList.get(0)).thenReturn("two");
when(mockedList.get(1)).thenThrow(new RuntimeException());
//following prints "two"
System.out.println(mockedList.get(0));
//following throws runtime exception
System.out.println(mockedList.get(1));
//following prints "null" because get(999) was not stubbed
System.out.println(mockedList.get(999));
//Although it is possible to verify a stubbed invocation, usually it's just redundant
//If your code cares what get(0) returns, then something else breaks (often even before verify() gets executed).
//If your code doesn't care what get(0) returns, then it should not be stubbed.
verify(mockedList).get(0);
demo2
@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的第一个元素是否是 "zuozwei"
Assert.assertEquals(result,"zuozewei");
}
常用方法
mock实例对象的常用方法
when(mockObject.actionMethod).thenReturn(String t) 设置方法的目标返回值
when(mockObject.actionMethod).thenThrow(Throwable... throwables) 让方法抛出异常
when(mockObject.actionMethod).thenAnswer(Answer answer) / then(Answer answer) 自定义方法处理逻辑
when(mockObject.actionMethod).thenCallRealMethod() 调用 spy 对象的真实方法
mock类对象的常用方法
mockStaticObject.when(Verification verification).thenReturn(...)
MockedStatic<QueryFactory> mockedQueryFactory = Mockito.mockStatic(QueryFactory.class)
mockedQueryFactory.when( () -> QueryFactory.query(any(), any(), any(), any()) ).thenReturn(result);
3.3 参数匹配:精确匹配与模糊匹配(anyXXX)
import org.junit.Assert;
import org.junit.Test;
import java.util.List;
import static org.mockito.Mockito.*;
public class MockitoDemo {
@Test
public void test() {
List mockList = mock(List.class);
Assert.assertEquals(0, mockList.size());
Assert.assertEquals(null, mockList.get(0));
mockList.add("a"); // 调用 mock 对象的写方法,是没有效果的
Assert.assertEquals(0, mockList.size()); // 没有指定 size() 方法返回值,这里结果是默认值
Assert.assertEquals(null, mockList.get(0)); // 没有指定 get(0) 返回值,这里结果是默认值
when(mockList.get(0)).thenReturn("a"); // 指定 get(0)时返回 a
Assert.assertEquals(0, mockList.size()); // 没有指定 size() 方法返回值,这里结果是默认值
Assert.assertEquals("a", mockList.get(0)); // 因为上面指定了 get(0) 返回 a,所以这里会返回 a
Assert.assertEquals(null, mockList.get(1)); // 没有指定 get(1) 返回值,这里结果是默认值
// 参数匹配:精确匹配
when(mockStringList.get(0)).thenReturn("a");
when(mockStringList.get(1)).thenReturn("b");
Assert.assertEquals("a", mockStringList.get(0));
Assert.assertEquals("b", mockStringList.get(1));
// 参数匹配:模糊匹配
when(mockStringList.get(anyInt())).thenReturn("a"); // 使用 Mockito.anyInt() 匹配所有的 int
Assert.assertEquals("a", mockStringList.get(0));
Assert.assertEquals("a", mockStringList.get(1));
}
//eq(...)
@Test
public void test2(){
...
when(databaseConnectionManager.borrowConnector(eq(DsType2.INFLUXDB), eq(dataSource), any(Integer.class))).thenReturn(connector);
...
}
}
3.4 reset(mockObject) : 重置之前自定义的返回值和异常
import org.junit.Assert;
import org.junit.Test;
import static org.mockito.Mockito.*;
public class MockitoDemo {
static class ExampleService {
public int add(int a, int b) {
return a+b;
}
}
@Test
public void test() {
ExampleService exampleService = mock(ExampleService.class);
// mock 对象方法的默认返回值是返回类型的默认值
Assert.assertEquals(0, exampleService.add(1, 2));
// 设置让 add(1,2) 返回 100
when(exampleService.add(1, 2)).thenReturn(100);
Assert.assertEquals(100, exampleService.add(1, 2));
// 重置 mock 对象,add(1,2) 返回 0
reset(exampleService);
Assert.assertEquals(0, exampleService.add(1, 2));
}
@Test
public void test2() {
ExampleService exampleService = spy(new ExampleService());
// spy 对象方法调用会用真实方法,所以这里返回 3
Assert.assertEquals(3, exampleService.add(1, 2));
// 设置让 add(1,2) 返回 100
when(exampleService.add(1, 2)).thenReturn(100);
Assert.assertEquals(100, exampleService.add(1, 2));
// 重置 spy 对象,add(1,2) 返回 3
reset(exampleService);
Assert.assertEquals(3, exampleService.add(1, 2));
}
}
4 Mock 注解
@Mock 注解
@mock快速创建mock的方法,使用
@mock注解需要搭配MockitoAnnotations.openMocks(testClass)方法一起使用.
public class ArticleManagerTest {
@Mock
private ArticleCalculator calculator;
@Mock
private ArticleDatabase database;
@Mock
private UserProvider userProvider;
private ArticleManager manager;
@org.junit.jupiter.api.Test
void testSomethingInJunit5() {
// 初始化mock对象
MockitoAnnotations.openMocks(testClass);
//Mockito.mock(class);
Mockito.spy(class);
}
// 简化
// @BeforeEach
// void setUp() {
// MockitoAnnotations.openMocks(this);
// }
}
@Spy注解 与 Spy方法
spy 和 mock不同,不同点是:
spy 的参数是对象实例,mock 的参数是 class。
被 spy 的对象,调用其方法时默认会走真实方法。mock 对象不会。
@Spy注解需要搭配MockitoAnnotations.openMocks(testClass)
方法一起使用.
import org.junit.Assert;
import org.junit.Test;
import static org.mockito.Mockito.*;
class ExampleService {
int add(int a, int b) {
return a+b;
}
}
public class MockitoDemo {
// 测试 spy
@Test
public void test_spy() {
ExampleService spyExampleService = spy(new ExampleService());
// 默认会走真实方法
Assert.assertEquals(3, spyExampleService.add(1, 2));
// 打桩后,不会走了
when(spyExampleService.add(1, 2)).thenReturn(10);
Assert.assertEquals(10, spyExampleService.add(1, 2));
// 但是参数比匹配的调用,依然走真实方法
Assert.assertEquals(3, spyExampleService.add(2, 1));
}
// 测试 mock
@Test
public void test_mock() {
ExampleService mockExampleService = mock(ExampleService.class);
// 默认返回结果是返回类型int的默认值
Assert.assertEquals(0, mockExampleService.add(1, 2));
}
}
spy 对应注解 @Spy,和 @Mock 是一样用的。
import org.junit.Assert;
import org.junit.Test;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
import static org.mockito.Mockito.*;
class ExampleService {
int add(int a, int b) {
return a+b;
}
}
public class MockitoDemo {
@Spy
private ExampleService spyExampleService;
@Test
public void test_spy() {
MockitoAnnotations.openMocks(this);
Assert.assertEquals(3, spyExampleService.add(1, 2));
when(spyExampleService.add(1, 2)).thenReturn(10);
Assert.assertEquals(10, spyExampleService.add(1, 2));
}
}
@InjectMocks : 注解注入 mock 对象
mockito 会将 @Mock、@Spy 修饰的对象自动注入到 @InjectMocks 修饰的对象中。
注入方式有多种,mockito 会按照下面的顺序尝试注入:
构造函数注入
设值函数注入(set函数)
属性注入
package demo;
import java.util.Random;
public class HttpService {
public int queryStatus() {
// 发起网络请求,提取返回结果
// 这里用随机数模拟结果
return new Random().nextInt(2);
}
}
package demo;
public class ExampleService {
private HttpService httpService;
public String hello() {
int status = httpService.queryStatus();
if (status == 0) {
return "你好";
}
else if (status == 1) {
return "Hello";
}
else {
return "未知状态";
}
}
}
import org.junit.Assert;
import org.junit.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import static org.mockito.Mockito.when;
public class ExampleServiceTest {
@InjectMocks // 将httpService主动注入
private ExampleService exampleService = new ExampleService();
@Mock
private HttpService httpService;
@Test
public void test01() {
MockitoAnnotations.initMocks(this);
when(httpService.queryStatus()).thenReturn(0);
Assert.assertEquals("你好", exampleService.hello());
}
}
验证和断言: verify(...)方法
验证是校验待验证的对象是否发生过某些行为,Mockito中验证的方法是:verify
verify(mock).someMoethod("some arg");
verify(mock,times(100)).someMoethod("some arg");
使用verify验证(Junit的断言机制):
@Test
void check() {
Random random = Mockito.mock(Random.class);
System.out.println(random.nextInt());
Mockito.verify(random,Mockito.times(2)).nextInt();
}
Verify配合times()方法,可以校验某些操作发生的次数
//using mock
mockedList.add("once");
mockedList.add("twice");
mockedList.add("twice");
mockedList.add("three times");
mockedList.add("three times");
mockedList.add("three times");
// 默认调用1次
verify(mockedList).add("once");
verify(mockedList, times(1)).add("once");
// 自定义调用多次
verify(mockedList, times(2)).add("twice");
verify(mockedList, times(3)).add("three times");
// 从不调用
verify(mockedList, never()).add("never happened");
// atLeast()/atMost() 至少调用 / 之多调用
verify(mockedList, atMostOnce()).add("once");
verify(mockedList, atLeastOnce()).add("three times");
verify(mockedList, atLeast(2)).add("three times");
verify(mockedList, atMost(5)).add("three times");
// 超时验证
verify(mock, timeout(100)).someMethod();
verify(mock, timeout(100).times(1)).someMethod();
//只要 someMethod() 在 100 毫秒内被调用 2 次,就会通过
verify(mock, timeout(100).times(2)).someMethod();
//someMethod() 至少在 100 毫秒内被调用 2 次,就会通过
verify(mock, timeout(100).atLeast(2)).someMethod();
5 Mock测试场景
MockMVC 测试 (未亲测)
对于前后端分离的项目而言,无法直接从前端静态代码中测试接口的正确性,因此可以通过MockMVC来模拟HTTP请求。基于RESTful风格的SpringMVC的测试,可以测试完整的Spring MVC流程,即从URL请求到控制器处理,再到视图渲染都可以测试。
初始化MockMvc对象
@Autowired
private WebApplicationContext webApplicationContext;
private MockMvc mockMvc;
//在每个测试方法执行之前都初始化MockMvc对象
@BeforeEach
public void setupMockMvc() {
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
接口测试
1 controller 层
/**
* id:\\d+只匹配数字
* @param id
* @return
*/
@GetMapping("/user/{id:\\d+}")
public User getUserById(@PathVariable Long id) {
return userService.getById(id);
}
2 Mockito构建自定义返回结果
userService.getById
并没有返回结果,但是我们的测试并不关心userService.getById这个方法是否正常,只是在我们的测试中需要用到这个方法,所以我们可以Mock掉UserService的getById方法,自己定义返回的结果。
@MockBean
private UserService userService;
@Test
void getUserById() throws Exception {
User user = new User();
user.setId(1);
user.setNickname("yunqing");
//Mock一个结果,当userService调用getById的时候,返回user
doReturn(user).when(userService).getById(any());
//perform,执行一个RequestBuilders请求,会自动执行SpringMVC的流程并映射到相应的控制器执行处理
mockMvc.perform(MockMvcRequestBuilders
//构造一个get请求
.get("/user/1")
//请求类型 json
.contentType(MediaType.APPLICATION_JSON))
// 期望的结果状态 200
.andExpect(MockMvcResultMatchers.status().isOk())
//添加ResultHandler结果处理器,比如调试时 打印结果(print方法)到控制台
.andDo(MockMvcResultHandlers.print());
}
3 传参数
@Test
void getUserByUsername() throws Exception {
// perform : 执行请求 ;
mockMvc.perform(MockMvcRequestBuilders
//MockMvcRequestBuilders.get("/url") : 构造一个get请求
.get("/user/getUserByName")
//传参
.param("username","admin")
// 请求type : json
.contentType(MediaType.APPLICATION_JSON))
// 期望的结果状态 200
.andExpect(MockMvcResultMatchers.status().isOk());
}
4 期望返回结果集有两个元素
@Test
void getAll() throws Exception {
User user = new User();
user.setNickname("yunqing");
List<User> list = new LinkedList<>();
list.add(user);
list.add(user);
//Mock一个结果,当userService调用list的时候,返回user
when(userService.list()).thenReturn(list);
//perform,执行一个RequestBuilders请求,会自动执行SpringMVC的流程并映射到相应的控制器执行处理
mockMvc.perform(MockMvcRequestBuilders
//构造一个get请求
.get("/user/list")
//请求类型 json
.contentType(MediaType.APPLICATION_JSON))
// 期望的结果状态 200
.andExpect(MockMvcResultMatchers.status().isOk())
//期望返回的结果集合有两个元素
.andExpect(MockMvcResultMatchers.jsonPath("$.length()").value(2))
//添加ResultHandler结果处理器,比如调试时 打印结果(print方法)到控制台
.andDo(MockMvcResultHandlers.print());
}
5 测试Post请求
@Test
void insert() throws Exception {
User user = new User();
user.setNickname("yunqing");
String jsonResult = JSONObject.toJSONString(user);
//直接自定义save返回true
when(userService.save(any())).thenReturn(true);
// perform : 执行请求 ;
MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders
//MockMvcRequestBuilders.post("/url") : 构造一个post请求
.post("/user/insert")
.accept(MediaType.APPLICATION_JSON)
//传参,因为后端是@RequestBody所以这里直接传json字符串
.content(jsonResult)
// 请求type : json
.contentType(MediaType.APPLICATION_JSON))
// 期望的结果状态 200
.andExpect(MockMvcResultMatchers.status().isOk())
.andDo(MockMvcResultHandlers.print())
.andReturn();//返回结果
int statusCode = mvcResult.getResponse().getStatus();
String result = mvcResult.getResponse().getContentAsString();
//单个断言
Assertions.assertEquals(200, statusCode);
//多个断言,即使出错也会检查所有断言
assertAll("断言",
() -> assertEquals(200, statusCode),
() -> assertTrue("true".equals(result))
);
常用API
//使用jsonPaht验证返回的json中code、message字段的返回值
.andExpect(MockMvcResultMatchers.jsonPath("$.code").value("00000"))
.andExpect(MockMvcResultMatchers.jsonPath("$.message").value("成功"))
//body属性不为空
.andExpect(MockMvcResultMatchers.jsonPath("$.body").isNotEmpty())
// 期望的返回结果集合有2个元素 , $: 返回结果
.andExpect(MockMvcResultMatchers.jsonPath("$.length()").value(2));
Y 样例集合
Y.1 基于 Mockito 框架的测试类
Y.1.1 Code
import cn.johnny.bd.dataservice.biz.cansignal.service.SignalNamesMappingCacheService;
import cn.johnny.bd.dataservice.biz.dataservice.mapper.IQueryJobMapper;
import cn.johnny.bd.dataservice.common.debug.Print;
import cn.johnny.bd.dataservice.common.sqlTemplate.GlobalInlineVariableNamesEnum;
import cn.johnny.bd.dataservice.model.entity.SignalFieldMapping;
import com.google.common.cache.CacheStats;
import com.google.common.cache.LoadingCache;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.util.AopTestUtils;
import org.springframework.test.util.ReflectionTestUtils;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import static org.junit.Assert.assertTrue;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.mockito.Mockito.*;
//@ExtendWith(MockitoExtension.class)
// MockitoJUnitRunner 为开发者提供了 Mockito 框架使用情况的自动验证,以及自动执行 initMocks()
@RunWith(MockitoJUnitRunner.class) // SpringJUnit4ClassRunner / SpringRunner / MockitoJUnitRunner
public class MockitoTest {
private static final Logger logger = LoggerFactory.getLogger(MockitoTest.class);
// @Mock
// private String canParseMatrixConfigDBName; // [X] 无法 Mock final class
/**
* 使用 @Mock 注解封装需要被模拟调用的对象 | @Mock: 创建一个Mock.
*/
@Mock
private IQueryJobMapper queryJobMapper;
@Mock
private LoadingCache<String, Map<String, SignalFieldMapping>> signalNamesMappingCache;
/**
* 使用 @InjetcMock 注解是为了向里面添加 @Mock 注入的对象 | @InjectMocks: 创建一个实例,其余用@Mock(或@Spy)注解创建的mock将被注入到用该实例中。
*/
@InjectMocks
private SignalNamesMappingCacheService cacheService;
@Before
public void setup(){
ReflectionTestUtils.setField(cacheService, "canParseMatrixConfigDBName", "johnny_bdp");//final Class(String) 无法成为 Mock 对象。故通过这种 Spring 提供的 ReflectionTestUtils 工具注入该属性
// ReflectionTestUtils.setField(cacheService, "queryJobMapper", queryJobMapper);
// ReflectionTestUtils.setField(cacheService, "signalNamesMappingCache", signalNamesMappingCache);
}
/**
* @description
* // step1 ready dataset for test stubs(准备测试桩所需的基础数据集)
* // step2 setup test stubs(设置测试桩)
* // step3 Run the target test class and method (执行测试目标类的目标方法)
* // step4 assert , verify and end over(断言、验证与结束)
*/
@Test
public void getSignalNamesMappingTestSuccess(){
// step1 ready dataset for test stubs(准备测试桩所需的基础数据集)
List<SignalFieldMapping> signalList = new ArrayList<>();
SignalFieldMapping signalFieldMapping1 = new SignalFieldMapping(1, "bcm_pepsPowerMode", "bcm_peps_power_mode", "bcm_pepsPowerMode", "{\"0\":\"DEFAULT_POWERMODE\",\"1\":\"OFF_POWERMODE\",\"2\":\"ON_POWERMODE\",\"3\":\"RESERVE_POWERMODE\",\"4\":\"RESERVE_POWERMODE1\",\"5\":\"RESERVE_POWERMODE2\",\"6\":\"RESERVE_POWERMODE3\",\"7\":\" INVALID_POWERMODE\"}");
SignalFieldMapping signalFieldMapping2 = new SignalFieldMapping(2, "bcm_pepsKeyNotInCarInd", "bcm_peps_key_not_in_car_ind", "Keyisnotincarindication.钥匙离开车内提示.", "{\"0\":\"NO_WARNING\",\"1\":\" WARNING\"}");
signalList.add(signalFieldMapping1);
signalList.add(signalFieldMapping2);
CacheStats cacheStats = new CacheStats(1,1,1,1,1,1);
String globalInlineVariableNamesCode = GlobalInlineVariableNamesEnum.SIGNAL_NAMES_ORIGIN_MAPPING.getCode();
// step2 setup test stubs(设置测试桩)
when(queryJobMapper.getSignalFieldMapping("johnny_bdp")).thenReturn(signalList);
when(signalNamesMappingCache.stats()).thenReturn(cacheStats);
// step3 Run the target test class and method (执行测试目标类的目标方法)
cacheService.init();
Map<String, SignalFieldMapping> signalFieldMappingMap = cacheService.getSignalNamesMapping(globalInlineVariableNamesCode);
// step4 assert , verify and end over(断言、验证与结束)
if(!signalFieldMappingMap.isEmpty()){
logger.debug("signalFieldMappingMap is not empty, content as follows:");
Print.print(signalFieldMappingMap);
}
Assert.assertNotNull(signalFieldMappingMap);
}
/**
* 基本模拟测试1
* @reference-doc
* [1] 走进Java接口测试之Mock(概念篇) - Tencent Cloud - https://cloud.tencent.com/developer/article/1465589
*/
@Test
public void mockitoBaseTest(){
// 创建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);
logger.warn("result:{}", result);// zuozewei
// 验证方法调用, 若有异常时会直接抛 Exception
String verifyResult = verify(list).get(0);
logger.warn("verifyResult:{}", verifyResult);//null
//断言,list的第一个元素是否是 "zuozwei"
Assert.assertEquals(result,"zuozewei");
}
/**
* 基本模拟测试2
*/
@Test
public void mockBaseTest2(){
//一旦mock对象被创建了,mock对象会记住所有的交互。然后你就可以选择性的验证感兴趣的交互
//You can mock concrete classes, not only interfaces
// 你可以mock具体的类型,不仅只是接口
//创建 mock 对象,mock 1个 List 接口
// List mockedList = mock(List.class);
//若不使用静态导入,则:必须使用 Mockito 调用
// List mockedList = Mockito.mock(List.class);
LinkedList mockedList = mock(LinkedList.class);
//stubbing
// 测试桩
when(mockedList.get(0)).thenReturn("first");
when(mockedList.get(1)).thenThrow(new RuntimeException());
//following prints "first"
// 输出 “first”
System.out.println(mockedList.get(0));
//following prints "null" because get(999) was not stubbed
// 因为get(999) 没有打桩,因此输出null
System.out.println(mockedList.get(999));
//Although it is possible to verify a stubbed invocation, usually it's just redundant
//If your code cares what get(0) returns then something else breaks (often before even verify() gets executed).
//If your code doesn't care what get(0) returns then it should not be stubbed. Not convinced? See here.
// 验证 get(0) 被调用的次数
verify(mockedList).get(0);
// following throws runtime exception
// 抛出异常 | 因 get(1) 的测试桩是抛出异常
// System.out.println(mockedList.get(1));
}
/**
* 基本模拟测试3
*/
@Test
public void mockBaseTest3(){
// 生成 mock 对象
Foo mockFoo = mock(Foo.class);
// 测试桩
when(mockFoo.bool(anyString(), anyInt(), any(Object.class))).thenReturn(true);
// 验证/断言
Assert.assertTrue(mockFoo.bool("A", 1, "A"));
Assert.assertTrue(mockFoo.bool("B", 10, new Object()));
// 测试桩
when(mockFoo.bool(eq("false"), anyInt(), any(Object.class))).thenReturn(false);
// 验证/断言
Assert.assertFalse(mockFoo.bool("false", 10, new Object()));
}
}
class Foo {
boolean bool(String str, int i, Object obj) {
return false;
}
int in(boolean b, List<String> strs) {
return 0;
}
int bar(byte[] bytes, String[] s, int i) {
return 0;
}
}
Y.1.2 dependency
<!-- org.junit.runner.RunWith;org.junit.Assert;org.junit.Before;org.junit.Test -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<!-- 4.13.1 -->
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<!-- org.slf4j.Logger ; org.slf4j.LoggerFactory; -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<!-- 1.7.25 -->
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<!-- 1.7.25 -->
</dependency>
<!--
org.mockito.Mockito; org.mockito.MockedStatic;
org.mockito.junit.MockitoJUnitRunner;
org.mockito.InjectMocks; org.mockito.Mock;
org.mockito.ArgumentMatchers.any; org.mockito.ArgumentMatchers.eq
-->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<!-- 3.4.0 -->
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<!-- 3.4.0 -->
<version>${mockito.version}</version>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<!-- 3.4.0 -->
<version>${mockito.version}</version>
<scope>compile</scope>
<exclusions>
<exclusion>
<artifactId>mockito-core</artifactId>
<groupId>org.mockito</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.github.jsonzou</groupId>
<artifactId>jmockdata</artifactId>
<!-- 4.3.0 -->
<version>${jmockdata.version}</version>
<scope>test</scope>
</dependency>
Y.2 基于 spring 框架的测试类
Y.2.1 Code
import cn.johnny.bd.dataservice.TestApplication;
import cn.johnny.bd.dataservice.biz.cansignal.service.SignalNamesMappingCacheService;
import cn.johnny.bd.dataservice.biz.dataservice.mapper.IQueryJobMapper;
import cn.johnny.bd.dataservice.common.debug.Print;
import cn.johnny.bd.dataservice.common.sqlTemplate.GlobalInlineVariableNamesEnum;
import cn.johnny.bd.dataservice.model.entity.SignalFieldMapping;
//import com.github.jsonzou.jmockdata.MockConfig;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import javax.annotation.Resource;
import java.util.Map;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = TestApplication.class)
public class SpringJUnitTest {
private static final Logger logger = LoggerFactory
.getLogger(SpringJUnitTest.class);
@Resource
private IQueryJobMapper queryJobMapper;
@Resource
private SignalNamesMappingCacheService cacheService;
@Before
public void init() {
}
@Test
public void test(){
String globalInlineVariableNamesCode = GlobalInlineVariableNamesEnum.SIGNAL_NAMES_ORIGIN_MAPPING.getCode();
Map<String, SignalFieldMapping> signalFieldMappingMap = cacheService.getSignalNamesMapping(globalInlineVariableNamesCode);
Print.print(signalFieldMappingMap);
Assert.assertNotNull(signalFieldMappingMap);
}
}
////////////// TestApplication //////////////
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication
@ComponentScan({"cn.johnny"})
public class TestApplication {
public static void main(String[] args) {
new SpringApplicationBuilder(TestApplication.class).web(WebApplicationType.NONE).run(args);
}
}
Y.2.2 dependency
<!-- org.junit.runner.RunWith;org.junit.Assert;org.junit.Before;org.junit.Test -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<!-- 4.13.1 -->
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<!-- org.slf4j.Logger ; org.slf4j.LoggerFactory; -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<!-- 1.7.25 -->
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<!-- 1.7.25 -->
</dependency>
<!-- org.springframework.boot.test.context.SpringBootTest -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test</artifactId>
<version>2.3.12.RELEASE</version>
<!-- <scope>compile</scope> -->
</dependency>
<!-- org.springframework.test.context.junit4.SpringJUnit4ClassRunner; org.springframework.test.util.ReflectionTestUtils; -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<!-- 5.2.15.RELEASE -->
<scope>test</scope>
</dependency>
K 模拟测试框架小结
mock-server: API/接口测试框架
MockServer
mock-server - github
Mokito
Mockito是mocking框架,它让你用简洁的API做测试。而且Mockito简单易学,它可读性强和验证语法简洁。
Mockito是GitHub上使用最广泛的Mock框架,并与JUnit结合使用。
Mockito框架可以创建和配置mock对象。
使用Mockito简化了具有外部依赖的类的测试开发。
JMockData
jmockdata - github
Jmockdata 随机模拟 java 数据插件 - OSChina
小结
常用的mock框架有:EasyMock、JMock、Mockito、PowerMockito。比较常用的是Mockito。
比较项 | EasyMock | JMock | Mockito | PowerMockito |
---|---|---|---|---|
final方法 | 不支持 | 不支持 | 不支持 | 支持 |
private方法 | 不支持 | 不支持 | 不支持 | 支持 |
静态方法 | 不支持 | 不支持 | 支持 | 支持 |
SpringBoot依赖 | 实现较为复杂 | 实现较为复杂 | 默认依赖 | 基于Mockito扩展 |
API风格 | 略复杂 | 略复杂 | 简单 | 简单 |
X 参考文献
基于Java的单元测试框架Mockito - CSDN
mockito参数匹配_Mockito参数匹配器– any(),eq() - CSDN
单元测试利器-Mockito 中文文档 - CSDN
使用Mockito模拟Static静态方法 - CSDN
Mock工具类静态方法出现registeration must be deregist问题 - CSDN
Mockito超全用例文档 - CSDN 【推荐】
使用JUnit5,Mockito,Hamcrest进行单元测试 - Zhihu 【推荐】
走进Java接口测试之Mock(概念篇) - Tengcent Cloud
Java单元测试之Mock框架 - CSDN
Guava Cache 缓存数据被移除后的监听器RemovalListener - CSDN