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

Mockito单元测试:如何Spy嵌套方法内新建的HashMap?

解决嵌套方法内局部HashMap的监控问题

你当前的inputMapSpy完全没参与到业务代码的执行流程里——MyOtherClass.method2每次调用都会新建一个独立的HashMap实例,和你声明的Spy对象毫无关联,所以自然监控不到任何变化。下面是几种可行的解决思路:


方案1:Mock拦截method2的返回值(无需修改业务代码)

核心思路是让Arg.method2直接返回你创建的Spy Map,这样MyClass.method1拿到的就是被监控的实例。

public class MyClassTest {
    @InjectMocks
    private MyClass myClass;
    @Spy
    private Map<String, Object> inputMapSpy = new HashMap<>();

    @Test
    public void testMethod1WithSpiedMap() {
        // 把Arg实例转为Mock,拦截它的method2调用
        Arg arg1 = Mockito.mock(Arg.class);
        // 指定method2返回我们的Spy Map
        Mockito.when(arg1.method2(Mockito.anyInt())).thenReturn(inputMapSpy);
        
        myClass.method1(arg1, 0);
        
        // 验证Map状态
        assertTrue(inputMapSpy.size() == 1);
        assertTrue(inputMapSpy.containsKey("key"));
    }
}

如果需要保留method2的真实业务逻辑(不想完全Mock),可以先执行真实方法获取结果,再把结果同步到Spy Map,最后替换返回值:

@Spy
private Arg arg1 = new Arg();

@Test
public void testMethod1WithRealLogic() {
    // 先执行真实method2拿到原始数据
    Map<String, Object> realMap = arg1.method2(0);
    inputMapSpy.putAll(realMap);
    
    // 让method2返回Spy Map
    Mockito.doReturn(inputMapSpy).when(arg1).method2(0);
    
    myClass.method1(arg1, 0);
    
    assertTrue(inputMapSpy.size() == 1);
}

方案2:修改业务代码,抽离Map创建逻辑(更优雅的长期方案)

如果允许修改业务代码,可以把method2里的Map创建逻辑抽成一个可覆盖的方法,测试时通过Spy替换该方法的返回值。

修改MyOtherClass:

public class MyOtherClass {
    public Map<String, Object> method2(int arg2) {
        Map<String, Object> map = createMap();
        switch(arg2) {
            case 0: 
                map.put("key", new Object());
                break;
            default:
                break;
        }
        return map;
    }
    
    // 抽成protected方法,供测试时覆盖
    protected Map<String, Object> createMap() {
        return new HashMap<>();
    }
}

测试类代码:

public class MyClassTest {
    @InjectMocks
    private MyClass myClass;
    @Spy
    private Map<String, Object> inputMapSpy = new HashMap<>();
    @Spy
    private MyOtherClass arg1 = new MyOtherClass();

    @Test
    public void testMethod1WithSpiedMap() {
        // 让createMap返回Spy Map
        Mockito.doReturn(inputMapSpy).when(arg1).createMap();
        
        myClass.method1(arg1, 0);
        
        assertTrue(inputMapSpy.size() == 1);
    }
}

方案3:用PowerMock拦截HashMap构造方法(无源码修改的终极方案)

如果既不能改业务代码,又需要保留method2的完整逻辑,可以用PowerMock拦截new HashMap()的调用,强制返回你的Spy实例。

@RunWith(PowerMockRunner.class)
@PrepareForTest(MyOtherClass.class) // 指定要拦截构造方法的类
public class MyClassTest {
    @InjectMocks
    private MyClass myClass;
    @Spy
    private Map<String, Object> inputMapSpy = new HashMap<>();

    @Test
    public void testMethod1WithPowerMock() throws Exception {
        // 拦截无参构造的HashMap,返回Spy实例
        PowerMockito.whenNew(HashMap.class).withNoArguments().thenReturn(inputMapSpy);
        
        Arg arg1 = new Arg();
        myClass.method1(arg1, 0);
        
        assertTrue(inputMapSpy.size() == 1);
    }
}

注意:PowerMock会修改类加载机制,可能和部分测试框架存在兼容性问题,建议优先使用前两种方案。


内容的提问来源于stack exchange,提问作者PainIsAMaster

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 21:48:14