Spring Boot 项目中 JUnit 使用总结

Spring Boot 项目中 JUnit 使用总结

在 Spring Boot 项目开发中,spring-boot-starter-test 是一个常用的单元测试依赖库,集成了许多主流测试工具和框架。本文总结了常见的测试场景及 Mock 方法,同时记录了项目中SpringBoot版本升级导致JUnit4升级到JUnit5的必要原因,供参考。

1. spring-boot-starter-test 依赖介绍

spring-boot-starter-test 是 Spring Boot 推荐的测试依赖包,旨在简化测试相关的库配置。

需要显式添加到项目中(通常在 pom.xml 或 build.gradle 中手动引入),它会自动提供一整套测试工具。
spring-boot-starter-test 默认包含以下常用测试工具:

  • JUnit 5 (默认测试框架)
  • Mockito (Mock 工具)
  • AssertJ (更简洁的断言工具)
  • Hamcrest (匹配器断言)
  • JsonPath (JSON 解析工具)
  • Spring Test (测试 Spring 上下文和 Web 工具)

对于特殊场景(例如 Mock 静态方法),需要手动添加 PowerMock 依赖。

2. 常用测试方式与适用场景

测试方式 选用场景
Mockito 单元测试,模拟依赖;
MockMvc 测试 Controller 的逻辑和 HTTP 请求/响应;
@MockBean/@SpyBean 替换 Spring 上下文中的 Bean,适用于集成测试;
MockEnvironment 模拟 Spring 的环境变量和配置属性;
TestRestTemplate 测试整个应用程序的 HTTP 接口(集成测试);
PowerMock 需要 Mock 静态方法、构造函数、final 类等复杂场景;

根据需求和测试粒度选择合适的 Mock 方式,例如单元测试推荐 Mockito,集成测试可以选择 TestRestTemplate 或 MockMvc。

3. 示例详解

3.1 Mockito

特点

  • 轻量级:无需启动 Spring 上下文。
  • 用于单元测试,模拟依赖对象的行为。

示例

模拟服务层依赖:

java 复制代码
@SpringBootTest
public class UserServiceTest {

    @Mock
    private UserRepository userRepository; // 模拟依赖

    @InjectMocks
    private UserService userService; // 将 Mock 对象注入到被测对象中

    @Test
    public void testGetUserById() {
        User mockUser = new User(1L, "Alice");
        Mockito.when(userRepository.findById(1L)).thenReturn(Optional.of(mockUser));

        User user = userService.getUserById(1L);

        assertEquals("Alice", user.getName());
    }
}

3.2 @MockBean 和 @SpyBean

特点

  • Spring 提供的 Mock 对象,集成了 Spring 的上下文。
  • @MockBean:替换 Spring 上下文中的实际 Bean。
  • @SpyBean:部分模拟 + 调用真实方法。

示例

@MockBean

java 复制代码
@SpringBootTest
public class UserServiceIntegrationTest {

    @Autowired
    private UserService userService;

    @MockBean
    private UserRepository userRepository; // Mock Spring 上下文中的 Bean

    @Test
    public void testGetUserById() {
        User mockUser = new User(1L, "Alice");
        // 模拟 findById 方法,其他调用未设置模拟行为的方法返回 "Mockito 默认值(方法返回类型的默认值)
        Mockito.when(userRepository.findById(1L)).thenReturn(Optional.of(mockUser));

        User user = userService.getUserById(1L);

        assertEquals("Alice", user.getName());
        
        // 调用未设置模拟行为的 findAll
        List<User> users = userRepository.findAll();
        assertTrue(users.isEmpty()); // 默认返回空集合
    }
}

@SpyBean

java 复制代码
@SpringBootTest
public class UserServiceSpyTest {

    @SpyBean
    private UserService userService; // 部分 Mock + 调用真实方法

    @MockBean
    private UserRepository userRepository;

    @Test
    public void testGetUserByIdWithSpy() {
        User mockUser = new User(1L, "Alice");
        // 模拟 findById 方法,其他调用未设置模拟行为的方法调用真实逻辑
        Mockito.when(userRepository.findById(1L)).thenReturn(Optional.of(mockUser));
        User user = userService.getUserById(1L); 
        assertEquals("Alice", user.getName());
        
        // 调用未模拟的 findAll 方法(真实逻辑)
        List<User> users = userRepository.findAll();
        assertFalse(users.isEmpty()); // 调用真实逻辑,返回真实数据
        assertEquals(2, users.size());
    }
}

3.3 MockMvc

特点

  • 主要用于测试 Controller 层。
  • 无需启动 Web 服务器,模拟 HTTP 请求和响应。

示例

java 复制代码
@WebMvcTest(UserController.class)
public class UserControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private UserService userService; // 模拟依赖服务

    @Test
    public void testGetUserById() throws Exception {
        User mockUser = new User(1L, "Alice");
        Mockito.when(userService.getUserById(1L)).thenReturn(mockUser);

        mockMvc.perform(get("/users/1"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.name").value("Alice"));
    }
}   

3.4 TestRestTemplate

特点

  • 主要用于集成测试,适合测试 HTTP 接口。
  • TestRestTemplate 是 Spring 提供的工具类,可以方便地测试 HTTP 接口。

示例

java 复制代码
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class UserApiIntegrationTest {

    @Autowired
    private TestRestTemplate restTemplate;

    @Test
    public void testGetUserById() {
        ResponseEntity<User> response = restTemplate.getForEntity("/users/1", User.class);

        assertEquals(HttpStatus.OK, response.getStatusCode());
        assertEquals("Alice", response.getBody().getName());
    }
}  

3.5 Spring Boot Mock Environment

特点

  • 使用 MockEnvironment 来模拟 Spring 的环境变量和配置属性

示例

java 复制代码
@SpringBootTest
public class EnvironmentTest {

    @Test
    public void testMockEnvironment() {
        MockEnvironment environment = new MockEnvironment();
        environment.setProperty("app.name", "TestApp");

        assertEquals("TestApp", environment.getProperty("app.name"));
    }
} 

3.6 Mock静态方法 (PowerMock)

特点

  • 需要 Mock 静态方法、final 类时,使用 PowerMock。
  • 需要额外添加依赖。

示例

java 复制代码
@RunWith(PowerMockRunner.class)
@PrepareForTest(Utility.class) // 指定需要 Mock 的静态类
public class StaticMethodTest {

    @Test
    public void testStaticMethod() {
        PowerMockito.mockStatic(Utility.class);
        PowerMockito.when(Utility.staticMethod()).thenReturn("Mocked Response");

        String result = Utility.staticMethod();

        assertEquals("Mocked Response", result);
    }
}

Mockito 3.4+ 提供了对静态方法和 final 类的原生支持,而不需要 PowerMock。关于于这部分下文会详细介绍

4.Mock静态方法或final类(特殊性&兼容性)

4.1 JUnit 4 + PowerMock使用场景

在 Spring Boot 项目中,Mock 静态方法或 final 类需要使用 PowerMock,因为 Mockito 默认不支持对静态方法和 final 类的 Mock。以下是配置和实现步骤:

4.1.1 添加依赖

在 Spring Boot 项目中,spring-boot-starter-test 不包含 PowerMock,需要手动添加。

Maven 依赖

xml 复制代码
<dependency>
    <groupId>org.powermock</groupId>
    <artifactId>powermock-module-junit4</artifactId>
    <version>2.0.9</version> <!-- 确保与项目 JUnit 版本兼容 -->
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.powermock</groupId>
    <artifactId>powermock-api-mockito2</artifactId>
    <version>2.0.9</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-inline</artifactId>
    <version>4.11.0</version> <!-- 支持 final 类的 Mock -->
    <scope>test</scope>
</dependency>
4.1.2 示例
Mock 静态方法

通过 PowerMock 的 mockStatic 方法 Mock 静态方法。

假设我们有一个静态工具类 Utility:

java 复制代码
public class Utility {
    public static String getStaticMessage() {
        return "Original Message";
    }
}

测试代码:

java 复制代码
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

@RunWith(PowerMockRunner.class) // 使用 PowerMockRunner
@PrepareForTest(Utility.class)  // 指定需要 Mock 的类
public class UtilityTest {

    @Test
    public void testStaticMethod() {
        PowerMockito.mockStatic(Utility.class); // Mock 静态方法

        Mockito.when(Utility.getStaticMessage()).thenReturn("Mocked Message");

        String result = Utility.getStaticMessage();

        assertEquals("Mocked Message", result); // 验证 Mock 是否生效
    }
}
Mock Final 类

示例

假设我们有一个 final 类:

java 复制代码
public final class FinalClass {
    public String sayHello() {
        return "Hello!";
    }
}

测试代码:

java 复制代码
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

@RunWith(PowerMockRunner.class) // 使用 PowerMockRunner
@PrepareForTest(FinalClass.class)  // 指定需要 Mock 的类
public class FinalClassTest {

    @Test
    public void testFinalClassMethod() throws Exception {
        FinalClass finalClassMock = PowerMockito.mock(FinalClass.class); // Mock Final 类

        Mockito.when(finalClassMock.sayHello()).thenReturn("Mocked Hello!");

        String result = finalClassMock.sayHello();

        assertEquals("Mocked Hello!", result); // 验证 Mock 是否生效
    }
}
4.1.3 Spring Boot 集成测试中的配置

如果需要在 Spring Boot 的测试中使用 PowerMock,可以将 PowerMockRunner 替换为 Spring 的测试上下文支持。

示例

java 复制代码
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.springframework.boot.test.context.SpringBootTest;

@RunWith(PowerMockRunner.class)
@PrepareForTest(Utility.class) // 指定需要 Mock 的类
@SpringBootTest // 启用 Spring 上下文
public class UtilitySpringBootTest {

    @Test
    public void testStaticMethodInSpring() {
        PowerMockito.mockStatic(Utility.class);

        Mockito.when(Utility.getStaticMessage()).thenReturn("Mocked in Spring Context");

        String result = Utility.getStaticMessage();

        assertEquals("Mocked in Spring Context", result);
    }
}

4.2 JUnit5使用场景

在使用 JUnit 5 的项目中,PowerMock 并未完全支持 JUnit 5,因此无法直接将 PowerMock 与 JUnit 5 一起使用。这是因为 PowerMock 的核心依赖于特定的类加载机制,而 JUnit 5 的架构与 JUnit 4 不同,没有提供类似 Runner 的机制。

JUnit 5使用Mockito-inline有两种方式可以启用对 final 类或静态方法的支持

4.2.1. 在 pom.xml 中添加依赖 方式

直接引入 mockito-inline 的 Maven 依赖,是最简单和推荐的方式。

xml 复制代码
<!--Maven依赖-->
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-inline</artifactId>
    <version>5.x.x</version> <!-- 请根据需要替换为适当版本 -->
    <scope>test</scope>
</dependency>
gradle 复制代码
// gradle依赖
dependencies {
    testImplementation 'org.mockito:mockito-inline:5.x.x' // 请替换为适当版本
}
4.2.2 使用配置文件 mockito-extensions/org.mockito.plugins.MockMaker方式

如果不引入 mockito-inline 依赖,也可以通过在 src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker 文件中添加以下内容来手动开启功能:

arduino 复制代码
mock-maker-inline
两种方式的区别
特性 通过依赖启用 (mockito-inline) 通过配置文件启用 (mock-maker-inline)
简洁性 简单,直接添加依赖即可生效 需要手动创建配置文件
粒度控制 全局生效,对整个测试项目启用 灵活,可按模块或文件级别配置
社区支持 官方推荐,现代项目通用方式 不再主流,仅适用部分老旧项目
适用性 现代项目(Spring Boot、JUnit 5) 老旧项目或特殊场景
易维护性 高,升级时直接跟随版本 低,容易被遗忘或误删
选择建议
  1. 如果可以直接引入依赖,优先使用 mockito-inline 方式,简洁易用。
  2. 如果受环境限制(例如某些构建工具或依赖约束),可以使用配置文件的方式作为替代。
  3. 不建议混用两种方式,可能导致不可预期的问题。
4.2.3 示例
Mock final 类
java 复制代码
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;

import static org.junit.jupiter.api.Assertions.assertEquals;

class FinalClass {
    public final String sayHello() {
        return "Hello!";
    }
}

public class FinalClassTest {

    @Test
    public void testFinalClassMethod() {
        FinalClass finalClassMock = Mockito.mock(FinalClass.class);

        Mockito.when(finalClassMock.sayHello()).thenReturn("Mocked Hello!");

        assertEquals("Mocked Hello!", finalClassMock.sayHello());
    }
}
Mock 静态方法

Mockito 3.4+ 引入了对静态方法 Mock 的原生支持,推荐使用这种方式。

java 复制代码
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.*;

class Utility {
    public static String getStaticMessage() {
        return "Original Message";
    }
}

public class StaticMethodTest {

    @Test
    public void testStaticMethod() {
        try (MockedStatic<Utility> mockedStatic = mockStatic(Utility.class)) {
            mockedStatic.when(Utility::getStaticMessage).thenReturn("Mocked Message");

            assertEquals("Mocked Message", Utility.getStaticMessage());
        }
    }
}

4.3 小结

场景 解决方案 推荐级别
Mock 静态方法 Mockito 3.4+ 原生 MockStatic ⭐⭐⭐⭐⭐
Mock final 类或方法 Mockito-inline ⭐⭐⭐⭐⭐
必须使用 PowerMock 回退到 JUnit 4 ⭐⭐⭐
更复杂需求 (如私有方法等) JMockit ⭐⭐⭐

5. SpringBoot版本升级对JUnit的影响

SpringBoot项目升级时( 2.2.5.RELEASE→2.7.18),升级了SpringBoot的版本导致大量的JUnit代码报错,通过Maven依赖源码以及官方资料查询了一番总结如下:

Spring Boot 2.2.5.RELEASE(依赖含JUnit4和JUint5)升级到Spring Boot 2.7.18(依赖只含JUint5)

  • SpringBoot2.2.0之前,spring-boot-starter-test引入的是JUnit4
  • 2.2.0 < SpringBoot < 2.4.0,spring-boot-starter-test默认使用JUnit5,同时也兼容支持JUnit4
  • SpringBoot2.4.0之后,spring-boot-starter-test默认仅支持JUnit5,去掉了兼容JUnit4引擎
附:Maven 层级依赖解析(spring-boot-starter-test)
plaintext 复制代码
spring-boot-starter-test
    |
    +--> spring-boot-test
    |
    +--> spring-boot-test-autoconfigure
    |
    +--> 第三方依赖 (JUnit, Mockito, AssertJ, 等)
相关推荐
.生产的驴24 分钟前
SpringBoot 开启热部署 项目热启动 一键调试无需 无需重启
java·运维·开发语言·spring boot·后端·spring·eclipse
Iam傅红雪3 小时前
修改springboot的配置文件
java·spring boot·spring
假装我不帅4 小时前
asp.net repeater嵌套
后端·asp.net
文浩(楠搏万)4 小时前
Java Spring Boot 项目中嵌入前端静态资源:完整教程与实战案例
java·服务器·前端·spring boot·后端·nginx·github
毕业设计-014 小时前
0005.基于SpringBoot+LayUI客户关系管理系统
spring boot·后端·layui
JavaCool4 小时前
🚀电商之经典营销活动-日周月多场次设计开发
java·后端
Q_19284999064 小时前
基于Spring Boot的校园部门资料管理系统
java·spring boot·后端
JohnYan4 小时前
对非对称加密的再思考
javascript·后端·安全
Python私教5 小时前
spacy快速入门
后端·算法