【Test】单元测试之Mockito

禹德水
2023-12-01

背景

单元测试的重要性,怎么说呢,按照流程规范来讲,每次上线前应该把单元测试都跑一遍,确认通过率是100%才可以。否则我们对现有代码做过修改后,仅依靠测试同学回归验证的话,很容易有场景遗漏,或者隐含未被发现的问题,而且修改公共部分的时候,可能测试的回归量也是很大的。但是,现实情况一般都是,业务代码都写不过来,哪有时间写单测,更别提有质量的单测。

我们目前项目单测的要求是,行覆盖率60%以上,通过率100%。很多时候为了这个指标都是过后才补单测,但其实,这样意义不大。应该在开发业务代码的时候完成单元测试,并用单测来验证代码业务逻辑的正确性。单元测试和代码注释,应该时刻和代码保持一致。

实践

类的注入

@MockBean: 类的所有方法都需要mock时,使用该注解
@SpyBean: 类的部分方法需要mock时,使用该注解

Mock使用

package com.yst.b2b.dms.controller.dealer.xls;

import com.alibaba.fastjson.JSON;
import com.yst.b2b.dms.TestApplication;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Matchers;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.context.WebApplicationContext;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * MockTest
 *
 * @author xyang010
 * @date 2020/8/23
 */
@RunWith(SpringRunner.class)
@SpringBootTest(classes = TestApplication.class, webEnvironment = SpringBootTest.WebEnvironment.MOCK)
@Transactional
public class MockTest {

    @Autowired
    private WebApplicationContext webApplicationContext;
    protected MockMvc mockMvc;
    @Mock
    protected HttpServletResponse response;
    @Mock
    protected HttpServletRequest request;

    @Before
    public void setUp() {
        mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
    }

    @MockBean
    private UserService userService;
    @SpyBean
    private OrderService orderService;

    /**
     * mock service方法的controller接口单测
     * @throws Exception
     */
    @Test
    public void getUserInfo() throws Exception {
        Mockito.doReturn(new UserInfo()).when(userService).getUserInfo(Matchers.any());
        MvcResult mvcResult = mockMvc
                .perform(MockMvcRequestBuilders
                        .post("/getUserInfo")
                        .param("userName", Matchers.any())
                        .accept(MediaType.APPLICATION_JSON_UTF8)
                        .contentType(MediaType.APPLICATION_JSON_UTF8)
                )
                .andDo(MockMvcResultHandlers.print())
                .andReturn();
        MyResult<UserInfo> result = JSON.parseObject(mvcResult.getResponse().getContentAsString(), new TypeReference<MyResult<UserInfo>>() {});
        Assert.assertTrue(result.isSuccess());
    }

    /**
     * 直接测试service的方法
     */
    @Test
    public void getOrderInfo() {
        OrderInfo orderInfo = orderService.getOrderInfo(1L);
        Assert.assertNotNull(orderInfo);
    }

    /**
     * 测试service方法, mock该方法内调用的同一service内的其他方法
     */
    @Test
    public void getOrderInfoMock() {
        Mockito.doNothing().when(orderService).checkParam(Matchers.any());
        Mockito.doNothing().when(orderService).checkOrderExist(Matchers.any());
        orderService.getOrderInfo(1L);
    }

    /**
     * 测试异常场景
     */
    @Test(expected = RuntimeException.class)
    public void getOrderInfoMock() {
        Mockito.doNothing().when(orderService).checkParam(Matchers.any());
        Mockito.doNothing().when(orderService).checkOrderExist(Matchers.any());
        orderService.getOrderInfo(-1L);
    }

}

注意事项

  • mock方法时,必须使用Matchers.any()作为参数生成,如执行具体值,mock不会生效,也可能会报错
  • 参数的方法必须是基本类型的包装类,使用基本类型会出问题,因为Matchers.any()可能生成的值是null,赋值给基本类型时不会自动转换为默认值,而会抛出Mockito异常

借鉴

Use Mockito to mock some methods but not others

Mockito的方法

Mockito模拟返回类型为void的方法

mockito中两种部分mock的实现,spy、callRealMethod

MockMVC 测试文件上传带参数

SpringBootTest使用Mock测试文件上传

No qualifying bean of type [org.springframework.web.context.WebApplicationContext]

Junit测试Controller(MockMVC使用),传输@RequestBody数据解决办法

 类似资料: