
本文介绍在不启动spring容器的前提下,使用mockito对依赖@autowired外部服务的spring service类进行单元测试的正确方法,重点解决因未手动注入mock对象导致的空指针异常问题。
在Spring应用中,@Autowired 依赖注入由Spring IoC容器在运行时自动完成;但在纯JUnit + Mockito的单元测试中(未启用@SpringBootTest),Spring容器并未启动,因此@Autowired字段不会被自动赋值——这正是externalService为null并引发NullPointerException的根本原因。
正确的做法是绕过Spring容器,手动完成依赖注入。以下是推荐的、符合测试最佳实践的完整方案:
✅ 推荐写法:使用构造函数注入 + Mockito.spy()(推荐)
首先,重构MyService以支持构造函数注入(更利于测试且符合Spring官方推荐):
@Service
public class MyService {
private final ExternalService externalService;
// 构造函数注入(推荐!)
public MyService(ExternalService externalService) {
this.externalService = externalService;
}
public String methodToTest(String myArg) {
String response = externalService.call(myArg);
// 处理逻辑...
return "processed: " + response;
}
}对应测试类如下(简洁、安全、无需反射或字段赋值):
@ExtendWith(MockitoExtension.class)
class MyServiceTest {
@Mock
private ExternalService externalService;
private MyService myService;
@BeforeEach
void setUp() {
myService = new MyService(externalService); // 显式传入Mock
}
@Test
void methodToTest_Test() {
// 给Mock定义行为
when(externalService.call("test")).thenReturn("mocked-response");
// 执行被测方法
String result = myService.methodToTest("test");
// 验证结果与交互
assertThat(result).isEqualTo("processed: mocked-response");
verify(externalService).call("test");
}
}⚠️ 若无法修改原Service(如遗留代码):手动字段赋值(次选)
若必须保留@Autowired字段注入且不能改构造函数,则需在测试中通过反射或直接赋值注入Mock(注意:spy(new MyService())本身不会初始化externalService,必须显式设置):
@ExtendWith(MockitoExtension.class)
class MyServiceTest {
@Mock
private ExternalService externalService;
private MyService myService;
@BeforeEach
void setUp() {
myService = new MyService(); // 使用无参构造创建实例
// 手动注入Mock(利用反射或直接赋值,此处假设字段为package-private或public)
// 方式1:直接赋值(要求字段非private)
// myService.externalService = externalService;
// 方式2:反射注入(推荐用于private字段)
ReflectionTestUtils.setField(myService, "externalService", externalService);
}
@Test
void methodToTest_Test() {
when(externalService.call("test")).thenReturn("mocked-response");
String result = myService.methodToTest("test");
assertThat(result).contains("mocked-response");
}
}? 关键提示: 避免使用 Mockito.spy(new MyService()) 后再尝试@Mock字段注入——spy()仅包装实例,不改变其内部字段状态; @InjectMocks 注解在字段注入场景下可能失效(尤其当存在多个构造函数或复杂依赖时),手动构造+注入更可控; 始终优先采用构造函数注入,它使依赖关系显式化、可测试性更强,也符合Spring Boot 2.6+ 的默认警告策略(spring.main.allow-circular-references=false等场景更稳健)。
通过以上方式,即可彻底规避NullPointerException,写出稳定、可维护、符合Spring工程规范的Service层单元测试。










