Mockito简要入门

原创
2021/03/31 17:42
阅读数 3.2K

Mockito协助JUnit进行测试

Mockito介绍

什么是mock测试

在写单元测试的过程中,我们往往会遇到要测试的类有很多依赖,这些依赖的类/对象/资源又有别的依赖,从而形成一个大的依赖树,要在单元测试的环境中完整地构建这样的依赖,是一件很困难的事情。

mock 测试就是在测试过程中,对于某些不容易构造或者不容易获取的对象,用一个虚拟的对象来创建以便测试的测试方法。这个虚拟的对象就是mock对象。mock对象就是真实对象在调试期间的代替品,方便测试独立的代码逻辑。

Mock测试框架的好处

  1. 可以很简单的虚拟出一个复杂对象(比如虚拟出一个接口的实现类);
  2. 可以配置 mock 对象的行为;
  3. 可以使测试用例只注重测试流程与结果;
  4. 减少依赖给单元测试带来的耦合。

Mock本质上是一个Proxy代理模式的应用

Mock本质上是一个Proxy代理模式的应用。如果Mockito.when传入的是一个普通对象的方法,那么只消费方法返回值是无法完成对该方法打桩的。所以Mockito本质上就是在代理对象调用方法前,用stub的方式设置其返回值,然后在真实调用时,用代理对象返回起预设的返回值。 
mockito用于创建代理类(生成字节码)的工具---bytebuddy
详见: https://zhuanlan.zhihu.com/p/87523954

常见mock框架

Mockito(本文采用)、TestableMock(阿里,暂时没有正式版)、PowerMock(这个也不错)、JMockit、EasyMock

从JUnit两个注解开始

@RunWith 和 @SpringBootTest

@RunWith就是一个运行器,告诉junit的框架应该是使用哪个testRunner
@RunWith(JUnit4.class)就是指用JUnit4来运
@RunWith(Suite.class)的话就是一套测试集合
而我们常用的@RunWith(SpringRunner.class),注解的意义在于Test测试类要使用注入的类,比如@Autowired注入的类,有了@RunWith(SpringRunner.class)这些类才能实例化到spring容器中,自动注入才能生效。
详见: https://www.cnblogs.com/qingmuchuanqi48/p/11886618.html

SpringJUnit4ClassRunner和SpringRunner区别。
SpringRunner 继承了SpringJUnit4ClassRunner,没有功能扩展,相当于换了个名字。
但SpringRunner对Junit有版本要求。如果是在 4.3 之前,只能选择 SpringJUnit4ClassRunner,如果是 4.3 之后,建议选择 SpringRunner。
--------------------------------------------------------
@SpringBootTest 是在Spring Test之上的再次封装,增加了切片测试,增强了mock能力。
功能测试过程中的几个关键要素及支撑方式如下:
    测试运行环境:通过@RunWith 和 @SpringBootTest启动spring容器。
    mock能力:Mockito提供了强大mock功能。
    断言能力:AssertJ、Hamcrest、JsonPath提供了强大的断言能力。
    
一旦依赖了spring-boot-starter-test,下面这些类库将被一同依赖进去:
    JUnit:java测试事实上的标准,默认依赖版本是4.xx(JUnit 5和JUnit 4差别比较大,集成方式有不同)。
    Spring Test & Spring Boot Test:Spring的测试支持。
    AssertJ:提供了流式的断言方式。
    Hamcrest:提供了丰富的matcher。
    Mockito:mock框架,可以按类型创建mock对象,可以根据方法参数指定特定的响应,也支持对于mock调用过程的断言。
    JSONassert:为JSON提供了断言功能。
    JsonPath:为JSON提供了XPATH功能。
详见: https://www.cnblogs.com/myitnews/p/12330297.html

自定义idea的SprigbootTest模板

引导

File->Settings->Editor->File and Code Templates->"+" Name: SpringBootTest Extension: java 如何使用: 创建"Java Class"时,最下面会多一项"SpringBootTest"

模板内容
#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME};#end
#parse("File Header.java")

import org.junit.After;
import org.junit.Before;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
public class ${NAME} {
    /**
     * 初始化方法
     */
    @Before
    public void before(){
        
    }
    
    /**
     * 释放资源方法
     */
    @After
    public void after(){
        
    }
}

Mockito如何接入

maven方式

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>3.8.0</version>
    <scope>test</scope>
</dependency>

(推荐)spring-boot接入 ,引入spring-boot-starter-test

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

Mockito常用操作及演示

常用注解

先看个栗子

@RunWith(SpringRunner.class)
@SpringBootTest
public class ServiceTest {
    //这是一个真实的对象
    @Spy
    private KeySequenceService keySequenceService;
    //这一个Mock对象
    @Mock
    private OverseasCapitalAccountMapper overseasCapitalAccountMapper;
    //依赖KeySequenceService和OverseasCapitalAccountMapper
    @InjectMocks
    private OverseasCapitalAccountService overseasCapitalAccountService;

    @Before
    public void before() throws Exception {
        //初始化。 会对@InjectMocks标识的对象,注入依赖。
        MockitoAnnotations.initMocks(this);
        
        //keySequenceService是真实的实例。
        //我们Mock 这个方法:keySequenceService.generateSeqNo(),传入任意字符串,都返回随机数。
        Mockito.doReturn(new Random().nextLong())
                .when(keySequenceService)
                .generateSeqNo(Mockito.anyString());

        //overseasCapitalAccountService.save() 底层调用overseasCapitalAccountMapper.insert()完成存库。
        //这里我们mock了overseasCapitalAccountMapper.insert()的实现
        //我们定义insert()传入任何OverseasCapitalAccount对象,都应该返回 1 。
        Mockito.when(overseasCapitalAccountMapper.insert()传入任何(Mockito.any(OverseasCapitalAccount.class)))
            .thenReturn(1);
    }

    /**
     * Method: generateCaNo()
     */
    @Test
    public void testGenerateCaNo() throws Exception {
        Long caNo = keySequenceService.generateSeqNo(OverseasCapitalAccountService.key_overseas_caNo);
        OverseasCapitalAccount account = new OverseasCapitalAccount();
        account.setCaNo(caNo);
        boolean save = overseasCapitalAccountService.save(account);
        Assert.assertTrue(save);

    }
} 

@Mock - 用于生成模拟对象

@Spy - 把一个真实对象包装成模拟对象

@InjectMocks - 根据类型对构造方法,普通方法和字段进行依赖注入.

注意:
通过注解方式(@Mock)生成mock对象,我们必需在初始化时使用如下代码,不然即使标注@Mock注解也会是null:
MockitoAnnotations.initMocks(testClass);  <-(推荐)
也可以使用: @RunWith(MockitoJUnitRunner.class)

@Mock与@Spy的区别

@Spy声明的对象,对函数的调用均执行真正部分。  通过doReturn()+when() 来配置我们需要的自定义返回的方法。 其他未配置方法,则返回真实调用结果。
@Mock声明的对象,对函数的调用均执行mock(即虚假函数),不会执行真实操作。  通过when()+thenXXX()配置我们需要的自定义返回的方法。 其他未配置方法,则返回对象默认值。
举个栗子
	//-- 例
	@Test
    public void test1(){
        //spy对象
        List<Integer> realList = new LinkedList<>();
        List<Integer> spyList = spy(realList);
        doReturn(99).when(spyList).size();
        Assert.assertEquals(99, spyList.size());
        Assert.assertThrows(IndexOutOfBoundsException.class, ()->{spyList.get(0);});
        try{
            Integer i = spyList.get(0);
            log.info("spyList.get(0): {}", i);
        }catch (IndexOutOfBoundsException e){
            log.error("",e);
        }

        //mock对象
        List<Integer> mockList = mock(List.class);
        when(mockList.size()).thenReturn(77);
        Assert.assertEquals(77, mockList.size());
        Assert.assertNull(mockList.get(0));
        Assert.assertNull(mockList.get(1));
        Assert.assertNull(mockList.get(55));
        Assert.assertNull(mockList.get(99));
        try{
            Integer i = mockList.get(100);
            log.info("mockList.get(100): {}", i);
        }catch (IndexOutOfBoundsException e){
            log.error("",e);
        }
    }

常用方法

mock() -虚拟对象。

功能同@Mock,但不需要初始化(见常用注解)。 如下例:

Map<String,Object> map = Mockito.mock(Map.class);

spy() -包装一个真实的对象。

功能同@Spy注解,但不需要初始化(见常用注解)。如下例:

// 创建一个LinkedList
List<String> list = new LinkedList<>();
//包装它
List<String> spyObj = spy(list);

anyXXX() -生成任意指定(或不指定)类型的对象

any()
any(Class class) 
anyInt()
anyString()
anyList()
anyMap()
...... 

when() -可以理解为:定义 ,一般与thenXXX()或doReturn()配合使用。

thenXXX() - 一般与when配合使用。

when(操作).thenXXX(返回/抛异常/执行操作并返回)  -》 定义此操作,应该返回/抛异常/执行操作并返回

几种常见then方法:	
  thenReturn(T obj) 返回obj
  thenThrow(E exception)  抛指定(exception)异常
  then(Answer a)/thenAnswer(Answer a)   执行(Answer)操作并返回     

测试Controller

主要的工具类

MockMvc对象
MockMvc实现了对Http请求的模拟,能够直接使用网络的形式,转换到Controller的调用。
MockMvcBuilders类
MockMvcBuilder构造MockMvc对象。 有两个静态方法:
	webAppContextSetup: 指定WebApplicationContext,将会从该上下文获取相应的控制器并得到相应的MockMvc
	standaloneSetup: 通过参数指定一组控制器,这样就不需要从上下文获取了。 既可以指定Controller。

举个栗子

@RunWith(SpringRunner.class)
@SpringBootTest
public class ControllerTest {
    @Autowired
    WebApplicationContext context;

    @Autowired
    private ObjectMapper objectMapper;

//    @Autowired
//    private DemoController demoController;

    private MockMvc mockMvc;

    /**
     * 初始化方法
     */
    @Before
    public void before() {
//        //测试指定controller;可以指定多个
//        mockMvc = MockMvcBuilders.standaloneSetup(demoController).build();

//        当要测试所有Controller或一个指定的url
        mockMvc= MockMvcBuilders.webAppContextSetup(context).build();
    }

    @SneakyThrows
    @Test
    public void test_get(){
    //DemoController.testGet()
        String param_name = "param";
        String param_value = "Tom";

        MvcResult result= mockMvc.perform(
                MockMvcRequestBuilders.get("/demo/testGet").param(param_name, param_value))
                .andReturn();
        //请求信息
        MockHttpServletRequest request=result.getRequest();
      
        //响应信息
        MockHttpServletResponse response = result.getResponse();
        String string = response.getContentAsString(CharsetUtil.CHARSET_UTF_8);
        //响应: {"code":0,"msg":null,"result":"Tom"}
        log.info("response : {}", string);

        Map<String,Object> resMap = objectMapper.readValue(string, Map.class);
        String res_param_value = (String) resMap.get("result");
        Assert.assertEquals(param_value, res_param_value);
    }

    @SneakyThrows
    @Test
    public void test_saveCapitalAccount(){
    //DemoController.saveCapitalAccount()
        String url = "/demo/opof/capitalAccount/save";

        Random random = new Random();
        OverseasCapitalAccountSaveReq saveReq = new OverseasCapitalAccountSaveReq();
//        saveReq.setCusSystem(3);//当传入非法参数时,会被校验器拦截,但这个case不会报错,应该对响应内容(如错误码)进行判断。
        saveReq.setCusSystem(2);
        saveReq.setBankAccount("bank-"+(random.nextInt(8999)+1000) );
        saveReq.setChannelUid("u"+(random.nextInt(899999)+100000) );
        saveReq.setCurrency("USD");
        saveReq.setEcifId(String.valueOf(random.nextInt(8999)+1000) );
        String jsonParam = objectMapper.writeValueAsString(saveReq);

        MvcResult result = mockMvc.perform(
                MockMvcRequestBuilders.post(url)
                    .contentType(MediaType.APPLICATION_JSON)
                    .characterEncoding(CharsetUtil.UTF_8)
                    .content(jsonParam)
        ).andReturn();
        MockHttpServletResponse response = result.getResponse();
        String string = response.getContentAsString(CharsetUtil.CHARSET_UTF_8);
        log.info("response : {}", string);
    }
}

资料&文档

https://site.mockito.org/  官网
https://www.javadoc.io/doc/org.mockito/mockito-core/2.7.12/org/mockito/Mockito.html  官方文档
https://www.jianshu.com/p/7d602a9f85e3
https://segmentfault.com/a/1190000006746409
https://my.oschina.net/u/4382383/blog/3526029
https://blog.csdn.net/wwh578867817/article/details/51934404
https://www.jianshu.com/p/a07ac78a6d86
https://blog.csdn.net/dnc8371/article/details/106699739/
https://blog.csdn.net/zhuqiuhui/article/details/88602589   --@RunWith(MockitoJUnitRunner.class) vs MockitoAnnotations.initMocks(this)
https://www.jianshu.com/p/6a21edd66f4a   MockMVC使用总结
https://www.cnblogs.com/jpfss/p/10950904.html   MockMvc详解
https://www.cnblogs.com/xuyatao/p/8337087.html  Junit测试Controller(MockMVC使用),传输@RequestBody数据解决办法
https://www.cnblogs.com/jpfss/p/10950904.html  MockMvc详解

Q/A(问答)

ReflectionUtils和ReflectionTestUtils

ReflectionUtils是Spring中一个常用的类,属于spring-core包;ReflectionTestUtils则属于spring-test包。两者功能有重叠的地方,而ReflectionUtils会更强大。在单元测试时使用ReflectionTestUtils,能增加我们的便利性。
叁见: https://blog.csdn.net/wolfcode_cn/article/details/80660515
展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
打赏
0 评论
0 收藏
2
分享
返回顶部
顶部