Mockito单元测试中ServiceClass调用UtilsTwo.callMethod()触发空指针异常的解决方案求助
Fixing NullPointerException in Mockito Test When Using @Spy on a Class with Dependencies
我完全懂你碰到的这个问题——用Mockito做单元测试时,@Spy标注的UtilsOne里的UtilsTwo居然是null,导致调用callMethod()时直接触发空指针异常,而且你还不想修改现有类结构,还要验证elements列表的内容对吧?别担心,不用调整业务代码结构就能解决这个问题。
为什么会出现这个空指针?
核心原因是:Mockito的
@Spy注解创建的是部分模拟对象(基于真实的UtilsOne实例包装而来),但它不会自动注入UtilsOne内部的依赖(也就是UtilsTwo)。当真实的UtilsOne方法执行到UtilsTwo.callMethod()时,UtilsTwo还是未初始化的null状态,自然就抛出NPE了。
解决方案步骤
我们只需要给Spy的UtilsOne手动注入Mock的UtilsTwo,就能解决空指针问题,同时保留原有测试逻辑:
- 模拟
UtilsTwo对象:先给UtilsTwo创建一个Mock实例,避免空指针同时还能控制它的行为 - 将Mock的
UtilsTwo注入到Spy的UtilsOne中:通过反射工具手动完成依赖注入(不用改业务类) - 正常执行测试并验证结果:保持
ServiceClass的@InjectMocks配置,执行测试后直接断言elements列表的内容
完整测试代码示例
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.test.util.ReflectionTestUtils; import java.util.ArrayList; import java.util.List; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) public class ServiceClassTest { // 1. 模拟UtilsTwo,避免空指针并可控制其行为 @Mock private UtilsTwo utilsTwo; // 2. 创建UtilsOne的Spy实例(保留真实方法逻辑) @Spy private UtilsOne utilsOne; // 3. 把依赖注入到ServiceClass中 @InjectMocks private ServiceClass serviceClass; @Test void testMethodOne() { // 关键操作:将Mock的utilsTwo注入到Spy的utilsOne内部 ReflectionTestUtils.setField(utilsOne, "utilsTwo", utilsTwo); // 准备测试数据 Object object1 = new Object(); Object object2 = new Object(); List<Element> elements = new ArrayList<>(); Object testType = new Object(); String testId = "test-123"; // (可选)如果需要控制UtilsTwo的行为,可以在这里设置 doNothing().when(utilsTwo).callMethod(testType, testId); // 执行待测试方法 serviceClass.methodOne(object1, object2, elements); // 验证elements列表的内容(根据你的业务逻辑调整断言) assertFalse(elements.isEmpty()); Element addedElement = elements.get(0); assertEquals(testType, addedElement.getType()); // 假设Element有getType()方法 assertEquals(testId, addedElement.getId()); // 假设Element有getId()方法 // (可选)验证UtilsTwo的方法是否被正确调用 verify(utilsTwo, times(1)).callMethod(testType, testId); } }
额外说明
- 如果你不用Spring框架,也可以用原生Java反射完成注入,替代
ReflectionTestUtils:try { Field field = UtilsOne.class.getDeclaredField("utilsTwo"); field.setAccessible(true); field.set(utilsOne, utilsTwo); } catch (NoSuchFieldException | IllegalAccessException e) { e.printStackTrace(); fail("Failed to inject UtilsTwo into UtilsOne"); } - 选择
@Spy是完全正确的:因为你希望UtilsOne的大部分方法执行真实逻辑,只有依赖部分需要Mock,这正是@Spy的设计场景,不需要改成@Mock。 - 关于
elements的验证:因为你传入的是真实的ArrayList实例,methodOne执行后会直接修改这个列表,所以直接断言列表内容即可,不需要额外Mock处理。
内容的提问来源于stack exchange,提问作者Eyal Hadad
相关产品推荐
相关产品推荐

