[Java SE/Junit] 基于Java的单元测试框架Mockito
0 序
- 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/接口测试框架
Mokito
- Mockito是mocking框架,它让你用简洁的API做测试。而且Mockito简单易学,它可读性强和验证语法简洁。
- Mockito是GitHub上使用最广泛的Mock框架,并与JUnit结合使用。
- Mockito框架可以创建和配置mock对象。
- 使用Mockito简化了具有外部依赖的类的测试开发。
JMockData
小结
常用的mock框架有:EasyMock、JMock、Mockito、PowerMockito。比较常用的是Mockito。
| 比较项 | EasyMock | JMock | Mockito | PowerMockito | 
|---|---|---|---|---|
| final方法 | 不支持 | 不支持 | 不支持 | 支持 | 
| private方法 | 不支持 | 不支持 | 不支持 | 支持 | 
| 静态方法 | 不支持 | 不支持 | 支持 | 支持 | 
| SpringBoot依赖 | 实现较为复杂 | 实现较为复杂 | 默认依赖 | 基于Mockito扩展 | 
| API风格 | 略复杂 | 略复杂 | 简单 | 简单 | 
Y FAQ for Mock
Q: 运行Mock测试用例方法时报InvalidTestClassError: Invalid test class 'com.xxx.demo.DemoApplicationTests' 1. No runnable methods
- 问题描述
运行Mock测试用例方法时报:
...
org.junit.runners.model.InvalidTestClassError: Invalid test class 'com.xxx.demo.DemoApplicationTests':
  1. No runnable methods
...
又例如:
org.mockito.exceptions.base.MockitoException: 
No tests found in xxxPeriodicCollectMessageParseServiceImplTest
Is the method annotated with @Test?
Is the method public?
	at org.mockito.internal.runners.RunnerFactory.create(RunnerFactory.java:82)
	at org.mockito.internal.runners.RunnerFactory.createStrict(RunnerFactory.java:40)
	at org.mockito.junit.MockitoJUnitRunner.<init>(MockitoJUnitRunner.java:154)
	at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499)
	at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:480)
	at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197)
	at java.base/java.util.ArrayList$ArrayListSpliterator.tryAdvance(ArrayList.java:1602)
	at java.base/java.util.stream.ReferencePipeline.forEachWithCancel(ReferencePipeline.java:129)
	at java.base/java.util.stream.AbstractPipeline.copyIntoWithCancel(AbstractPipeline.java:527)
	at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:513)
	at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)
	at java.base/java.util.stream.FindOps$FindOp.evaluateSequential(FindOps.java:150)
	at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
	at java.base/java.util.stream.ReferencePipeline.findFirst(ReferencePipeline.java:647)
	at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197)
	at java.base/java.util.ArrayList$ArrayListSpliterator.tryAdvance(ArrayList.java:1602)
	at java.base/java.util.stream.ReferencePipeline.forEachWithCancel(ReferencePipeline.java:129)
	at java.base/java.util.stream.AbstractPipeline.copyIntoWithCancel(AbstractPipeline.java:527)
	at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:513)
	at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)
	at java.base/java.util.stream.FindOps$FindOp.evaluateSequential(FindOps.java:150)
	at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
	at java.base/java.util.stream.ReferencePipeline.findFirst(ReferencePipeline.java:647)
Caused by: java.lang.reflect.InvocationTargetException
	at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499)
	at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:480)
	at org.mockito.internal.runners.util.RunnerProvider.newInstance(RunnerProvider.java:29)
	at org.mockito.internal.runners.RunnerFactory.create(RunnerFactory.java:75)
	... 22 more
Caused by: org.junit.runners.model.InvalidTestClassError: Invalid test class 'com.xxx.backend.service.periodiccollect.impl.xxxPeriodicCollectMessageParseServiceImplTest':
  1. No runnable methods
	at org.mockito.internal.runners.DefaultInternalRunner$1.<init>(DefaultInternalRunner.java:31)
	at org.mockito.internal.runners.DefaultInternalRunner.<init>(DefaultInternalRunner.java:30)
	... 26 more
- 问题分析
- 原因1: 可能是导错了
@Test注解 (20250404 亲测)应该使用
org.junit.Test
而非使用org.junit.jupiter.api.Test
- 原因2: 检查类、测试方法是否用
public修饰
- 参考文献
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
 
    本文链接: https://www.cnblogs.com/johnnyzen
关于博文:评论和私信会在第一时间回复,或直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
日常交流:大数据与软件开发-QQ交流群: 774386015 【入群二维码】参见左下角。您的支持、鼓励是博主技术写作的重要动力!

 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号