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
相关产品推荐
相关产品推荐

