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, 等)
相关推荐
ekskef_sef5 分钟前
Spring Boot——日志介绍和配置
java·数据库·spring boot
计算机-秋大田1 小时前
基于微信小程序的购物系统设计与实现(LW+源码+讲解)
java·后端·微信小程序·小程序·课程设计
GGBondlctrl1 小时前
【Spring Boot】掌握 Spring 事务:隔离级别与传播机制解读与应用
java·spring boot·spring·事务隔离级别·spring事务传播机制
ekskef_sef2 小时前
在2023idea中如何创建SpringBoot
java·spring boot·后端
Sao_E2 小时前
SpringBoot实现定时任务,使用自带的定时任务以及调度框架quartz的配置使用
java·spring boot·后端
码明2 小时前
SpringBoot整合junit
数据库·spring boot·junit
计算机萍萍学姐4 小时前
基于springboot的考研资讯平台
java·spring boot·后端
lozhyf4 小时前
JavaFx + SpringBoot 快速开始脚手架
java·spring boot·后端
m0_748235074 小时前
如何使用Spring Boot框架整合Redis:超详细案例教程
spring boot·redis·后端
ん贤5 小时前
Go 切片:用法和本质
开发语言·后端·golang