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) | 老旧项目或特殊场景 |
易维护性 | 高,升级时直接跟随版本 | 低,容易被遗忘或误删 |
选择建议
- 如果可以直接引入依赖,优先使用 mockito-inline 方式,简洁易用。
- 如果受环境限制(例如某些构建工具或依赖约束),可以使用配置文件的方式作为替代。
- 不建议混用两种方式,可能导致不可预期的问题。
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, 等)