
本文介绍在不启动 spring 容器的前提下,使用 mockito 对依赖 `@autowired` 外部服务的 spring service 进行单元测试的正确方法,重点解决因未手动注入 mock 导致的空指针异常问题。
在 Spring 应用中,@Autowired 字段注入仅在 Spring IoC 容器管理 Bean 的生命周期时生效(例如通过 @SpringBootTest 启动上下文)。而在纯单元测试(如使用 @ExtendWith(MockitoExtension.class))中,Spring 容器并未参与对象创建,因此 MyService 实例中的 externalService 字段保持为 null,直接调用会触发 NullPointerException。
正确的做法是:跳过 Spring 依赖注入机制,改用手动赋值方式将 Mock 对象注入目标 Service 实例。注意以下关键点:
- ✅ 使用 @Mock 声明并初始化 ExternalService 的 Mock 实例;
- ✅ 使用 new MyService() 构造原始实例(非 @Spy 或 @InjectMocks 的模糊代理);
- ❌ 避免 Mockito.spy(new MyService()) 后未注入依赖——spy 仅包装原对象,不自动处理字段赋值;
- ✅ 在测试方法内或 @BeforeEach 中,显式赋值 myService.externalService = externalService;
- ✅ 使用 Mockito.when(...).thenReturn(...) 定义 Mock 行为,推荐使用具体返回值(如 "mocked-response")而非 anyString()(后者在 thenReturn 中无效,应改为 thenReturn("test-result"))。
以下是修正后的完整示例代码:
@ExtendWith(MockitoExtension.class)
class MyServiceTest {
@Mock
private ExternalService externalService;
private MyService myService; // 不要在此处初始化
@BeforeEach
void setUp() {
myService = new MyService(); // 创建真实实例
myService.externalService = externalService; // 手动注入 Mock
}
@Test
void methodToTest_Test() {
// 定义 Mock 行为:避免使用 anyString() 作为返回值
when(externalService.call("input")).thenReturn("mocked-response");
// 执行被测方法
String result = myService.methodToTest("input");
// 断言
assertThat(result).isEqualTo("mocked-response");
verify(externalService).call("input");
}
}⚠️ 注意事项:
- @InjectMocks 虽可自动注入 @Mock 字段,但对 private 字段需配合 @Spy 或反射,稳定性不如手动赋值清晰可控;
- 若 MyService 构造函数依赖 ExternalService,建议重构为构造器注入(推荐),此时可直接在 new MyService(externalService) 中完成依赖传递,更符合测试友好与 Spring 最佳实践;
- 切勿在 thenReturn(anyString()) 中使用 anyString()——它仅用于匹配参数,不能作为返回值,否则将返回 null,引发后续 NPE。
总结:脱离 Spring 上下文的单元测试,本质是“普通 Java 对象测试”,所有依赖必须显式提供。手动注入 Mock 是最直接、可靠且易于理解的方式,兼顾可读性与可维护性。










