单元测试:JUnit 5 与 Mockito
本教程共 48 篇 · 第 39 篇 · 更新于 2026-08-13 · 约 4 分钟阅读
本节目标:会用
@SpringBootTest启动应用上下文,用 Mockito 模拟依赖,用 AssertJ 写断言,跑通第一个单元测试。
测试为什么值得写
没写过测试的代码,改起来像走钢丝。改一个方法,不知道哪里会炸。
测试就是安全网。改了代码跑一遍测试,绿的说明没改坏,红的说明踩到雷了。Spring Boot 把测试支持做得很全,加一个依赖就能开写。
加依赖:spring-boot-starter-test
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
一个 starter 打包了全套测试工具:
- JUnit 5(Jupiter):测试框架本体。
- AssertJ:流式断言,
assertThat(...)一串链下去。 - Hamcrest:老牌匹配器,现在用得少。
- Mockito:模拟对象。
- JSONassert / JsonPath:断言 JSON 用。
版本全部由 Spring Boot 统一管理,不用写版本号。
第一个测试:@SpringBootTest
测试类放在 src/test/java 下,包名和主类一致:
package com.example.demo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class DemoApplicationTests {
@Test
void contextLoads() {
// 应用上下文能启动,说明 Bean 装配没问题
}
}
@SpringBootTest 会通过 SpringApplication 创建完整的应用上下文。它自动从测试类所在包向上找 @SpringBootApplication 主类,不用手动指定。
Note默认情况下
@SpringBootTest不启动服务器。它加载的是 mock 的 Web 环境,专门配合 MockMvc 用。想启动真实端口,用webEnvironment属性,下一节会讲。
跑测试用 Maven:
mvn test
看到 Tests run: 1, Failures: 0 就是全绿。
断言:AssertJ 是主角
JUnit 5 自带 Assertions.assertEquals,但 AssertJ 的链式写法更顺:
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
class DemoAssertionTests {
@Test
void assertjStyle() {
String name = "Spring Boot";
List<String> tags = List.of("测试", "打包", "部署");
assertThat(name).startsWith("Spring").endsWith("Boot");
assertThat(tags).hasSize(3).contains("测试");
assertThat(tags).allMatch(tag -> tag.length() > 1);
}
}
失败时 AssertJ 会打出详细的对比信息:期望什么、实际是什么。排错体验比 assertEquals 好一截。
生命周期注解
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
class DemoLifecycleTests {
private String message;
@BeforeEach
void setUp() {
message = "ready"; // 每个测试方法执行前都会跑
}
@Test
@DisplayName("准备阶段生效")
void messageIsReady() {
assertThat(message).isEqualTo("ready");
}
}
常用的一套:@BeforeEach 准备数据,@AfterEach 清理现场,@DisplayName 给测试起中文名。测试名写清楚,失败时一眼看懂哪里不对。
Mockito:模拟外部依赖
单元测试要快、要稳定。可 Service 里常依赖数据库、第三方接口,一跑就牵连一堆。Mockito 的解法是造假:造一个「替身」对象,指定它返回什么,测试只关注当前类的逻辑。
纯单元测试:@Mock + @InjectMocks
不启动 Spring,纯 JUnit 5 风格:
package com.example.demo.service;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.verify;
@ExtendWith(MockitoExtension.class)
class GreetingServiceTest {
@Mock
private UserRepository userRepository; // 替身
@InjectMocks
private GreetingService greetingService; // 把替身注入进来
@Test
void greetReturnsUserName() {
given(userRepository.findNameById(1L)).willReturn("小明");
String result = greetingService.greet(1L);
assertThat(result).isEqualTo("你好,小明");
verify(userRepository).findNameById(1L); // 确认真的调用了
}
}
三个关键点:
@Mock:创建替身对象,方法默认返回 null / 空集合。@InjectMocks:把替身按类型注入被测对象。given(...).willReturn(...):指定替身行为,这是 BDD 风格,比when(...).thenReturn(...)读起来更像「安排」。
Mockito 的版本由 spring-boot-starter-test 管理,不需要单独声明 mockito-core。
Tip初学者常忘
@ExtendWith(MockitoExtension.class),@Mock注解不生效,对象全是 null。跑起来报空指针,先查这个。
在 Spring 测试里换 Bean:@MockitoBean
@SpringBootTest 场景下,想替换容器里的真实 Bean,用 @MockitoBean。这是 Spring Framework 提供的注解,Spring Boot 4.x 的主推写法,@MockBean 在 4.x 已移除,必须改用 @MockitoBean:
package com.example.demo.service;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
@SpringBootTest
class OrderServiceIntegrationTest {
@Autowired
private OrderService orderService;
@MockitoBean
private PaymentClient paymentClient; // 容器里的真实 Bean 被替换成替身
@Test
void orderTotalIncludesShipping() {
given(paymentClient.charge(100)).willReturn(true);
boolean paid = orderService.pay(100);
assertThat(paid).isTrue();
}
}
@MockitoBean 的效果:测试期间,容器中该类型的 Bean 换成 Mockito 替身,测试结束自动恢复。远程服务、短信通道这类不稳定依赖,一律用替身。
@TestConfiguration:给测试加料
有时候测试需要额外的配置 Bean,又不想污染主配置。用嵌套的 @TestConfiguration:
package com.example.demo.service;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
@SpringBootTest
class DemoWithExtraConfigTests {
@TestConfiguration(proxyBeanMethods = false)
static class ExtraConfig {
@Bean
String extraGreeting() {
return "测试专用问候";
}
}
@Test
void testSomething() {
// 上下文里多了一个 extraGreeting Bean
}
}
区别要分清:普通嵌套 @Configuration 会替换主配置,@TestConfiguration 是追加。它不会被组件扫描误抓,只在显式导入的测试里生效。
抓控制台输出:OutputCaptureExtension
要断言日志、System.out 输出,用 OutputCaptureExtension:
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.boot.test.system.CapturedOutput;
import org.springframework.boot.test.system.OutputCaptureExtension;
import static org.assertj.core.api.Assertions.assertThat;
@ExtendWith(OutputCaptureExtension.class)
class DemoOutputTests {
@Test
void capturesOutput(CapturedOutput output) {
System.out.println("Hello World!");
assertThat(output).contains("World");
}
}
上下文缓存:跑得快的小秘密
Spring 的测试框架会缓存应用上下文。多个测试类配置相同,上下文只启动一次,后面直接复用。所以别在测试里随意改配置属性——配置一变,缓存失效,又要重新启动。
Warning
@SpringBootTest是「重量级」测试,一个上下文启动可能要几秒。单元测试写多了,优先用切片测试(@WebMvcTest、@DataJpaTest)和纯 Mockito 测试,只加载需要的部分。下一节讲 Web 层切片。
小结
spring-boot-starter-test一个依赖带齐 JUnit 5、AssertJ、Mockito。- 断言首选 AssertJ,链式、报错信息友好。
- 纯逻辑用
@Mock+@InjectMocks;要 Spring 容器用@SpringBootTest+@MockitoBean。 - 测试命名用
@DisplayName写人话,失败时才知道测的是什么。