1. 概述
JUnit 是 Java 中最流行的单元测试框架之一。Spring Boot 更是将其作为应用的默认测试依赖。
本文将对比两个 JUnit 运行器:SpringRunner 和 MockitoJUnitRunner。我们将深入理解它们的用途和核心差异。
2. @RunWith 与 @ExtendWith 对比
在深入主题前,先回顾下如何扩展 JUnit 基础功能或与其他库集成。
JUnit 4 的扩展机制
JUnit 4 允许通过实现自定义 Runner 类来扩展功能,这些类负责运行测试并添加额外特性。使用 @RunWith
注解调用自定义运行器:
@RunWith(CustomRunner.class)
class JUnit4Test {
// ...
}
JUnit 5 的扩展机制
JUnit 4 已进入维护状态,被 JUnit 5 取代。新版本带来了全新的引擎和重写的 API,并改变了扩展模型概念。不再需要实现自定义 Runner
或 Rule
类,而是使用 Extension API 配合 @ExtendWith
注解:
@ExtendWith(CustomExtensionOne.class)
@ExtendWith(CustomExtensionTwo.class)
class JUnit5Test {
// ...
}
与旧版运行器模型不同,单个类可以指定多个扩展。大多数旧版运行器也已重写为对应的扩展。
3. Spring 示例应用
为更好理解,我们创建一个简单的 Spring Boot 应用——一个将字符串转换为大写的转换器。
数据提供者实现
@Component
public class DataProvider {
private final List<String> memory = List.of("baeldung", "java", "dummy");
public Stream<String> getValues() {
return memory.stream();
}
}
这是一个 Spring 组件,包含硬编码的字符串值,并提供流式访问方法。
服务层实现
@Service
public class StringConverter {
private final DataProvider dataProvider;
@Autowired
public StringConverter(DataProvider dataProvider) {
this.dataProvider = dataProvider;
}
public List<String> convert() {
return dataProvider.getValues().map(String::toUpperCase).toList();
}
}
该服务从 DataProvider
获取数据并应用大写转换。
现在我们用这个应用编写 JUnit 测试,对比 SpringRunner
和 MockitoJUnitRunner
的差异。
4. MockitoJUnitRunner
Mockito 是一个模拟框架,常与其他测试框架配合使用,用于返回虚拟数据并避免外部依赖。该库提供了 MockitoJUnitRunner——一个专用的 JUnit 4 运行器,用于集成 Mockito 并利用其能力。
基础测试示例
public class StringConverterTest {
@Mock
private DataProvider dataProvider;
@InjectMocks
private StringConverter stringConverter;
@Test
public void givenStrings_whenConvert_thenReturnUpperCase() {
Mockito.when(dataProvider.getValues()).thenReturn(Stream.of("first", "second"));
val result = stringConverter.convert();
Assertions.assertThat(result).contains("FIRST", "SECOND");
}
}
我们模拟了 DataProvider
使其返回两个字符串。但直接运行会失败:
java.lang.NullPointerException: Cannot invoke "DataProvider.getValues()" because "this.dataProvider" is null
常见踩坑原因
@Mock
和 @InjectMocks
注解未被初始化。有两种解决方案:
方案 1:手动初始化
@Before
public void init() {
MockitoAnnotations.openMocks(this);
}
方案 2:编程式创建
@Before
public void init() {
dataProvider = Mockito.mock(DataProvider.class);
stringConverter = new StringConverter(dataProvider);
}
使用 MockitoJUnitRunner 的优雅方案
移除 init()
方法,添加 @RunWith
注解:
@RunWith(MockitoJUnitRunner.class)
public class StringConverterTest {
// ...
}
测试成功!运行器自动管理了模拟对象,无需手动初始化。
核心功能总结
✅ MockitoJUnitRunner 是 Mockito 框架的专用运行器
✅ 自动初始化 @Mock
、@Spy
和 @InjectMock
注解
✅ 无需显式调用 MockitoAnnotations.openMocks()
✅ 自动检测未使用的存根,并在每个测试方法后验证模拟用法(类似 Mockito.validateMockitoUsage()
)
JUnit 5 迁移方案
@ExtendWith(MockitoExtension.class)
public class StringConverterTest {
// ...
}
MockitoExtension
将 MockitoJUnitRunner
的功能移植到新的扩展模型。
5. SpringRunner
深入分析测试会发现:尽管使用了 Spring,我们并未启动 Spring 容器。现在修改示例初始化 Spring 上下文。
基础集成测试
@RunWith(SpringRunner.class)
public class StringConverterTest {
// ...
}
测试成功!模拟对象被正确初始化,同时启动了 Spring 上下文。
关键特性
✅ SpringRunner 不仅支持 Mockito 注解(类似 MockitoJUnitRunner
)
✅ 同时初始化 Spring 上下文
完整 Spring 集成示例
@ContextConfiguration(classes = StringConverter.class)
@RunWith(SpringRunner.class)
public class StringConverterTest {
@MockBean
private DataProvider dataProvider;
@Autowired
private StringConverter stringConverter;
// ...
}
关键改进点
- 用
@MockBean
替代@Mock
:既是模拟对象又是 Spring Bean - 通过
@ContextConfiguration
配置 Spring 上下文 - 使用
@Autowired
注入StringConverter
核心功能总结
✅ SpringRunner 是 JUnit 4 的自定义运行器,提供 Spring TestContext 框架功能
✅ 完全支持 MockitoJUnitRunner
的所有能力(Mockito 是 Spring 默认集成框架)
✅ 支持 Spring 特有注解:@MockBean
和 @SpyBean
✅ 通过 Spring 上下文注入模拟对象
别名说明
⚠️ SpringJUnit4ClassRunner
是 SpringRunner
的别名,两者可互换使用
JUnit 5 迁移方案
@ExtendWith(SpringExtension.class)
public class StringConverterTest {
// ...
}
SpringExtension
已与 Spring Boot 的多个测试切片注解集成(如 @SpringBootTest
)。
6. 结论
本文深入对比了 SpringRunner
和 MockitoJUnitRunner
:
核心差异总结
特性 | MockitoJUnitRunner | SpringRunner |
---|---|---|
主要用途 | 纯 Mockito 集成 | Spring 上下文 + Mockito 集成 |
上下文初始化 | ❌ 不启动 Spring 容器 | ✅ 启动完整 Spring 容器 |
注解支持 | @Mock , @InjectMocks |
@MockBean , @SpyBean |
适用场景 | 简单单元测试 | 集成测试/需要 Spring 上下文的测试 |
选择建议
- ✅ 纯单元测试:使用
MockitoJUnitRunner
(JUnit 4)或MockitoExtension
(JUnit 5) - ✅ 需要 Spring 上下文:使用
SpringRunner
(JUnit 4)或SpringExtension
(JUnit 5) - ✅ Spring Boot 项目:优先使用
@SpringBootTest
+@ExtendWith(SpringExtension.class)
完整示例代码可在 GitHub 获取。