如何在JUnit+Mockito中Mock带@Inject@Any的Instance<SomeInterface>字段
正确Mock CDI Instance的单元测试方案
你遇到的问题核心在于:CDI的Instance<SomeInterface>是特殊的接口,它的迭代行为需要我们显式Stub,另外如果@InjectMocks未正确注入字段,也会触发NPE。下面是无需新增测试专用方法的优雅解决方案:
步骤1:正确Mock Instance及依赖
我们需要MockInstance<SomeInterface>,同时Stub它的iterator()方法,让它返回包含Mock的SomeInterface实现类的迭代器。另外如果@InjectMocks注入失败,还可以用反射手动补全字段注入(你的Mockito版本中@InjectMocks大概率能正常工作)。
完整测试代码示例
@RunWith(MockitoJUnitRunner.class) public class SomeTestClass { @InjectMocks private AnotherClass anotherClass; @Mock private Instance<SomeInterface> impls; // Mock两个实现类实例 @Mock private SomeInterface mockImplA; @Mock private SomeInterface mockImplB; @Test public void testSomeMethod_AllImplsReturnFalse_ReturnsNull() { // 让Instance返回包含两个mock实现的迭代器 when(impls.iterator()).thenReturn(Arrays.asList(mockImplA, mockImplB).iterator()); // Stub每个实现的oneMethod()返回false when(mockImplA.oneMethod()).thenReturn(false); when(mockImplB.oneMethod()).thenReturn(false); SomeInterface result = anotherClass.someMethod(); Assert.assertNull(result); } @Test public void testSomeMethod_FirstImplReturnsTrue_ReturnsFirstImpl() { when(impls.iterator()).thenReturn(Arrays.asList(mockImplA, mockImplB).iterator()); // 让第一个实现返回true when(mockImplA.oneMethod()).thenReturn(true); when(mockImplB.oneMethod()).thenReturn(false); SomeInterface result = anotherClass.someMethod(); Assert.assertEquals(mockImplA, result); } }
为什么之前的做法失败?
- 未Stub Instance的迭代行为:你之前的测试只Mock了
Instance对象,但没有Stubiterator()方法。Mockito默认会让Mock对象的方法返回null,当代码执行for(SomeInterface imp : impls)时,调用impls.iterator()得到null,进而抛出NPE(你可能误以为是impls为null,实际是迭代器为null)。 - @Spy的误用:
@Spy用于包装真实对象,但Instance是CDI接口,没有可用的真实实例可以Spy,所以应该用@Mock来创建纯粹的Mock对象。
备用方案:手动注入字段
如果@InjectMocks因为类代理等原因无法注入impls字段,可以用ReflectionTestUtils手动设置(需要引入spring-test依赖,或者自行编写反射工具类):
@Before public void setup() { ReflectionTestUtils.setField(anotherClass, "impls", impls); }
这样就能在不修改业务代码的前提下,完成对AnotherClass的单元测试。
内容的提问来源于stack exchange,提问作者jonesir
相关产品推荐
相关产品推荐

