likes
comments
collection
share

springboot使用mockito进行单元测试

作者站长头像
站长
· 阅读数 13

0.写在之前

1.开始使用

1.1 整理思路

  • 首先明确目标,我们是要通过单元测试提升代码的覆盖率,那在对每个方法进行测试时,不可避免就有以下几个步骤:

1、构造方法的入参以及想要让它返回的出参,构建不同的参数即可覆盖代码的不同分支。

2、进行打桩。打桩:在被测试的方法中,如果调用了其他类中的方法,可以在测试方法中对其进行打桩,让单元测试执行到此处时返回自己想要的结果,具体请参见下文。

3、执行被测试类的该方法获取结果,然后比较结果与预期是否相符。

1.2 引入依赖

  • 使用的springboot版本为2.3.2.RELEASEspring-boot-starter-test依赖包中包含有mockitojunit,但在mock静态方法时可能会出现版本问题,所以此处给出详细版本供参考。
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.testng</groupId>
    <artifactId>testng</artifactId>
    <version>6.14.3</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>3.8.0</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-inline</artifactId>
    <version>3.8.0</version>
    <scope>test</scope>
</dependency>

2.例

2.1 准备工作

  • 1、找到需要测试的类,如AppParamsServiceImpl,在与main同级的test目录下建立与该类包结构相同的测试类,可命名为AppParamsServiceImplTest

  • 2、为测试类添加注解@RunWith(MockitoJUnitRunner.class),并将被测试的类作为测试类的属性,为其添加注解@InjectMocks,将被测试类中要使用的接口也添加进来,并添加注解@Mock

  • 3、添加测试方法,为其添加注解@Test,一般使用test开头作为方法名,如testGetAppParams(),无需参数。

2.2 代码

  • 以下为一个被测试类测试类的例子,按 1.1 步骤编写,但一些测试类中并不需要包含全部步骤。已省略与单元测试无关的导入,请配合注释阅读。

2.2.1 被测试类

@Service
@Slf4j
public class AppParamsServiceImpl extends ServiceImpl<AppParamsMapper, AppParams> implements AppParamsService {
    
    @Resource
    AppParamsMapper appParamsMapper;

    @Override
    public AppResponse getAppParams(String id) {
        QueryWrapper<AppParams> wrapper = new QueryWrapper<>();
        wrapper.eq("app_params_id", id);
        wrapper.ne("state", "X");
        AppParams appParams = appParamsMapper.selectOne(wrapper);
        AppResponse appResponse = new AppResponse();
        BeanUtils.copyProperties(appParams, appResponse);
        return appResponse;
    }
}

2.2.2 测试类

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

@RunWith(MockitoJUnitRunner.class)
public class AppParamsServiceImplTest {

    @InjectMocks
    private AppParamsServiceImpl appParamsServiceImplUnderTest;
    
    @Mock
    private AppParamsMapper mockAppParamsMapper;

    @Test
    public void testGetAppParams() {
        // 定义期望的返回结果
        final AppResponse expectedResult = new AppResponse();
        expectedResult.setUrl("url");
        expectedResult.setApi("api");
        expectedResult.setAppId("appId");
        expectedResult.setAppKey("appKey");

        // 该执行:AppParams appParams = appParamsMapper.selectOne(wrapper);
        // 不想连接数据库进行查询,但想让单元测试继续进行,则对此处进行打桩
        // 为打桩做准备,构建想要其返回的结果
        final AppParams appParams = new AppParams();
        appParams.setAppParamsId("appParamsId");
        appParams.setUrl("url");
        appParams.setApi("api");
        appParams.setAppId("appId");
        appParams.setAppKey("appKey");
        // 打桩 当执行到selectOne方法,且参数为任意的QueryWrapper,则返回之前定义好的appParams
        when(mockAppParamsMapper.selectOne(any(QueryWrapper.class))).thenReturn(appParams);
        // 调用被测试类的方法进行测试
        final AppResponse result = appParamsServiceImplUnderTest.getAppParams("id");
        // 校验结果
        assertEquals(expectedResult, result);
    }
}

2.3 注意

2.3.1 常量

  • 如果被测试类中包含常量或者从配置文件中获取的变量,可使用以下方法进行mock:
import org.springframework.test.util.ReflectionTestUtils;
import org.junit.jupiter.api.BeforeEach;
import static org.mockito.MockitoAnnotations.initMocks;
/**
 * 在测试类中添加该方法,在每个测试方法中先调用setUp()对变量进行设置,或添加 @BeforeEach注解
 */
@BeforeEach
void setUp() {
    initMocks(this);
    ReflectionTestUtils.setField(appParamsServiceImplUnderTest,"channelId","channelId");
}

2.3.2 异常

  • 测试抛出异常的分支时可以使用assertThrows
import static org.testng.Assert.assertThrows;

@Test
public void testAssertUtil() {
    assertThrows(ValidateException.class, () -> AssertUtil.isNotNull(null, ValidateMessageCodeEnum.CHANNEL_ID_IS_NULL));
}

2.3.3 静态方法

  • 如果被测试类的方法中调用了静态类的静态方法,则与上方稍有不同,可使用以下方法进行mock:
import com.owner.mos.mops.infiniti.service.common.utils.HttpClientUtil;
import org.junit.Test;
import org.junit.jupiter.api.BeforeEach;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.util.ReflectionTestUtils;

import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.when;
import static org.mockito.MockitoAnnotations.initMocks;

@RunWith(MockitoJUnitRunner.class)
public class OwnerServiceTest {

    @InjectMocks
    private OwnerService ownerServiceUnderTest;
    
    @MockBean
    private MockedStatic<HttpClientUtil> httpClientUtilMockedStatic;

    @BeforeEach
    void setUp() {
        initMocks(this);
        // 对静态类进行mock
        httpClientUtilMockedStatic = mockStatic(HttpClientUtil.class);
    }

    @Test
    public void testNotify() {
        setUp();
        // 参数构造...
        
        // 期望结果
        final HttpRespVO expectedResult = new HttpRespVO("成功", new String[]{"操作成功"}, null);
        // mock方法返回值
        HttpRespDTO httpRespDTO = new HttpRespDTO();
        httpRespDTO.setMsg("操作成功");
        String resultJson = JSON.toJSONString(httpRespDTO);
        // 打桩
        httpClientUtilMockedStatic.when(() -> HttpClientUtil.doPostMethodNoVerify(anyString(), anyString(), anyMap())).thenReturn(resultJson);
        // 调用测试
        final HttpRespVO result = ownerServiceUnderTest.notify(notifyMessage);
        // 校验结果
        assertEquals(expectedResult, result);
        // 最后将其及时关闭
        httpClientUtilMockedStatic.close();
    }
}

2.3.4 打桩参数

  • 没有参数的可以不填,String类型可设为anyString(),其他的包装类型也类似。如:
when(mockOrderSequence.nextValue()).thenReturn("result");
httpClientUtilMockedStatic.when(() -> HttpClientUtil.doPostMethodNoVerify(anyString(), anyString(), anyMap())).thenReturn(resultJson);
  • 有参数的一般可直接设为any(),有时也可使用其他值,请按实际情况使用,如:
when(mockOrderService.addOrder(new OrderAddDto())).thenReturn(orderAddVo);
when(mockAppParamsMapper.selectOne(any(QueryWrapper.class))).thenReturn(appParams);
when(mockOrderMapper.update(eq(new Order()), any(LambdaQueryWrapper.class))).thenReturn(0);
when(mockRestTemplate.postForObject(anyString(), any(), eq(WsResponse.class), any(Object.class))).thenReturn(wsResponse);

3.插件

  • Squaretest,idea中一款单元测试插件,可按模板生成单元测试文件,节省一部分工作量,但较多一部分仍需按实际情况自行编写,且为免费试用30天。使用方法请读者自行搜索。

4.写在之后

转载自:https://juejin.cn/post/7312046910045732903
评论
请登录