Spring编写单元测试的工具介绍:JUnit、Mockito、AssertJ

背景:

在Spring应用程序中,想要通过代码走查做好测试左移,单元测试是确保代码质量和功能正确性的关键。除了我们常用的TestNG 外,本次介绍一下其他常见的单元测试工具: **JUnit、Mockito、AssertJ,**来提高我们做白盒测试的效率。

一、引入依赖

在Maven项目中,我们通过添加以下依赖来引入使用

1、JUnit

JUnit是最常用的Java单元测试框架之一,它提供了丰富的API来编写和组织测试用例。

<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-api</artifactId>
    <version>5.9.1</version>
    <scope>test</scope>
</dependency>

2、Mockito

Mockito允许我们创建模拟对象,并定义它们在测试中的行为。例如,我们可以模拟静态方法、构造函数或私有方法。可以用于模拟各个外部接口或者属性方法的数据。

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

3、AssertJ

AssertJ提供了丰富的断言方法,使得断言更加简洁和易读。它支持链式断言和软断言,使得测试代码更加清晰。

<dependency>
    <groupId>org.assertj</groupId>
    <artifactId>assertj-core</artifactId>
    <version>3.24.2</version>
    <scope>test</scope>
</dependency>

二、常见模拟用法

1. 静态类

使用MockedStatic @Cleanup注解自动close。

@cleanup Mockedstatic<URLDecoder> urlDecoderMockedstatic =
                        Mockito.mockstatic(URLDecoder.class);
                urlDecoderMockedstatic.when(()->URLDecoder.decode(
                        Mockito.any(),Mockito.anystring())).
                        thenReturn(JsoNutil.toJsonstr(depositTradeResp));

2. 工具类

使用mockConstruction

Mockito.mockConstruction(AesUtil.class, (util, context) -> {
    Mockito.when(util.decryptMsg(Mockito.anyString())).thenReturn(true);
});

3. 私有方法

使用反射机制

Method checkCouponByQuery = createGoOrderService.getClass().getDeclaredMethod("checkCouponByQuery", List.class, List.class, List.class);
checkCouponByQuery.setAccessible(true);

try {
    checkCouponByQuery.invoke(createGoOrderService, addedXPlusOrderInfoList, usableCoupon, checkCouponResultList);
} catch (Exception e) {
    logger.info("异常信息:{}", e.getMessage());
}

4. 局部模拟

 ReflectUtil.setFieldValue(changeOrderService, "changeInfoMapper", changeInfoMapperOne);
BDDMockito.when(changeInfoMapperOne.getSubSegOrderList()).thenReturn(Arrays.asList("sdsd", "sds"));

三、单测类的实现

下面这个基类为Spring Boot应用程序的测试提供了一个基础框架,包括测试环境的配置、测试数据的加载、以及测试过程中所需的工具和资源的管理。这有助于确保测试的一致性、可重复性和可靠性。其中loadJsonParam 方法允许测试类从类路径下的文件中加载JSON数据,这在测试需要发送JSON请求的API时很有用。init 方法在每个测试方法之前初始化 MockMvc 对象,这是测试Spring MVC应用程序的关键步骤。after 方法可以用来执行测试后的清理工作。最后,getMockMvc 和 getParamJsonStr 方法提供了对 MockMvc 对象和JSON参数字符串的访问,以便在测试中使用。

package com.csair.ecs;

import java.io.BufferedReader;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import org.junit.After;
import org.junit.Before;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

@RunWith(SpringRunner.class)
@WebAppConfiguration
@SpringBootTest(classes = GroupNeedServiceApplication.class)
@ActiveProfiles("test")
public class BaseTest {

    public static final String UTF_8 = "UTF-8";

    @Autowired
    private WebApplicationContext webAppConfiguration;

    private MockMvc mockMvc;

    private String paramJsonStr;

    public void loadJsonParam(String classPathJsonFileName) {
        StringBuilder sb = new StringBuilder(300);
        try (BufferedInputStream inputStream = new BufferedInputStream(
                Thread.currentThread().getContextClassLoader().getResourceAsStream(classPathJsonFileName));
             BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, UTF_8))) {
            String temp;
            while ((temp = reader.readLine())!= null) {
                sb.append(temp);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        this.paramJsonStr = sb.toString();
    }

    // 在每个测试方法执行之前,初始化MockMvc对象。
    @Before
    public void init() {
        mockMvc = MockMvcBuilders.webAppContextSetup(webAppConfiguration).build();
    }

    // 在每个测试方法执行之后,可以执行清理工作。
    @After
    public void after() {
    }

    // 提供获取MockMvc对象的方法,以便在测试中使用。
    public MockMvc getMockMvc() {
        return mockMvc;
    }

    // 提供获取JSON参数字符串的方法,以便在测试中使用。
    public String getParamJsonStr() {
        return paramJsonStr;
    }
}

四、测试类实现

下面这个测试类通过继承 BaseTest 类,利用已经配置好的测试环境和工具。每个测试方法都设计来覆盖特定的功能点,确保服务在各种输入下都能正确执行。使用 Lombok 的 @Slf4j 注解简化了日志记录的配置,使得测试代码更加简洁。

@Slf4j
public class SystemParamsConfigServiceImplTest extends BaseTest {

    @Autowired
    private SystemParamsConfigService systemParamsConfigService;

    @Test
    public void testSelectParamsPageList() {
        // Test case 1
        SystemParamsConfigReq req1 = new SystemParamsConfigReq();
        req1.getPage().setPageNo(1);
        req1.getPage().setPageSize(5);
        ResponseBase<PageData<SystemParamsConfig>> responseBase1 = systemParamsConfigService.selectParamsPageList(req1);
        log.info("结果:{}", JSONUtil.parseObj(responseBase1));

        // Test case 2
        SystemParamsConfigReq req2 = new SystemParamsConfigReq();
        req2.getPage().setPageNo(1);
        req2.getPage().setPageSize(5);
        req2.setParamKey("dd");
        ResponseBase<PageData<SystemParamsConfig>> responseBase2 = systemParamsConfigService.selectParamsPageList(req2);
        log.info("结果:{}", JSONUtil.parseObj(responseBase2));

        // Test case 3
        SystemParamsConfigReq req3 = new SystemParamsConfigReq();
        req3.getPage().setPageNo(1);
        req3.getPage().setPageSize(5);
        req3.setRemark("d");
        ResponseBase<PageData<SystemParamsConfig>> responseBase3 = systemParamsConfigService.selectParamsPageList(req3);
        log.info("结果:{}", JSONUtil.parseObj(responseBase3));
    }

    @Test
    public void testGetByParamKey() {
        ResponseBase<SystemParamsConfig> responseBase = systemParamsConfigService.getByParamKey("aa");
        log.info("结果:{}", JSONUtil.parseObj(responseBase));
    }

    @Test
    public void testInsertParamConfig() {
        SystemParamsConfig paramsConfig = new SystemParamsConfig();
        paramsConfig.setParamKey(RandomUtil.randomNumbers(6));
        paramsConfig.setParamValue("测试一下");
        paramsConfig.setOperator("wangjiansheng9");
        paramsConfig.setRemark("备注一下");

        // Test case 1
        ResponseBase<Integer> responseBase1 = systemParamsConfigService.insertParamConfig(paramsConfig);
        log.info("结果:{}", JSONUtil.parseObj(responseBase1));
        setParamKey(paramsConfig.getParamKey());

        // Test case 2
        paramsConfig.setParamKey("ferferfrefrefrefer");
        ResponseBase<Integer> responseBase2 = systemParamsConfigService.insertParamConfig(paramsConfig);
        log.info("结果:{}", JSONUtil.parseObj(responseBase2));
    }
}

五、IDEA查看覆盖率

在IntelliJ IDEA中,鼠标右键选中某个单元测试类或包,选择 更多运行/调试 - 使用覆盖率 - 使用覆盖率运行。

覆盖率

等待单元测试运行结束,会在IDEA右侧弹出覆盖率面板。在代码中查看具体覆盖情况。

相关推荐
Joeysoda2 小时前
JavaEE进阶(2) Spring Web MVC: Session 和 Cookie
java·前端·网络·spring·java-ee
howard20053 小时前
1.4 单元测试与热部署
spring boot·单元测试·热部署
曹二7473 小时前
Spring-事务
数据库·spring·oracle
时雨h4 小时前
芋道源码 —— Spring Boot 缓存 Cache 入门
java·后端·spring
blammmp5 小时前
Java EE 进阶:Spring MVC(2)
spring·java-ee·mvc
Nijika...7 小时前
libilibi项目优化(1)使用Redis实现缓存
java·数据库·redis·后端·spring·缓存
1 Byte10 小时前
Spring为什么要用三级缓存解决循环依赖?
java·spring·循环依赖
小湘西11 小时前
ShardingSphere 和 Spring 的动态数据源切换机制的对比以及原理
java·数据库·spring
獨枭1 天前
在 IntelliJ IDEA 中使用 JUnit 进行单元测试
junit·单元测试·intellij-idea
sky丶Mamba1 天前
Spring框架中的单例Bean是线程安全的吗
java·安全·spring