You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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,就能解决空指针问题,同时保留原有测试逻辑:

  1. 模拟UtilsTwo对象:先给UtilsTwo创建一个Mock实例,避免空指针同时还能控制它的行为
  2. 将Mock的UtilsTwo注入到Spy的UtilsOne中:通过反射工具手动完成依赖注入(不用改业务类)
  3. 正常执行测试并验证结果:保持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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.27 16:58:14