[Java SE/Junit] 基于Java的单元测试框架Mockito

2023-05-12,,

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框架有:EasyMockJMockMockitoPowerMockito。比较常用的是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

[Java SE/Junit] 基于Java的单元测试框架Mockito的相关教程结束。

《[Java SE/Junit] 基于Java的单元测试框架Mockito.doc》

下载本文的Word格式文档,以方便收藏与打印。