被测类中Mock依赖返回对象为Null,测试类中却正常的问题解决
Mockito测试中MyService Mock返回Null的解决方法
被测类代码
public class MyClass { private final MyService myService; public MyClass(MyService myService) { this.myService = myService; } public BigDecimal doSomething(Long id) { MyEntity myEntity = myService.getEntity(id); MySubEntity mySubEntity = myEntity.getSubEntity(); Long myId = mySubEntity.getId(); BigDecimal result = BigDecimal.ZERO; // misc operations return result; } }
测试类代码(原问题版本)
@SpringBootTest @ExtendWith(MockitoExtension.class) @MockitoSettings public class MyClassTests { @Mock private MyService myService; @InjectMocks private MyClass myClass; @Test public void testDoSomething() { // GIVEN MyEntity myEntity = mock(MyEntity.class); MySubEntity mySubEntity = mock(MySubEntity.class); BigDecimal result = new BigDecimal("3.1415926"); // WHEN when(myService.getEntity(any())).thenReturn(myEntity); when(myEntity.getSubEntity()).thenReturn(mySubEntity); when(mySubEntity.getId()).thenReturn(42L); // THEN assertEquals(result, myClass.doSomething(36L)); } }
问题现象
运行测试时抛出NullPointerException,调试发现MyClass中myEntity变量为null,但测试类内部的Mock对象状态正常。
原因分析
核心问题是注解冲突:
@SpringBootTest会启动完整的Spring上下文,Spring自行管理bean的创建与注入逻辑@ExtendWith(MockitoExtension.class)是Mockito独立测试的扩展,负责Mock对象初始化和@InjectMocks的注入
两者混用会导致注入逻辑冲突,Spring上下文的初始化可能覆盖Mockito的Mock配置,使得myService的Stub设置未生效,调用getEntity时返回null。
另外,原测试中使用any()匹配器类型不够精确,也可能导致Stub匹配失败。
解决方案
方案1:纯Mockito独立测试(推荐,测试速度更快)
移除@SpringBootTest,仅保留Mockito相关注解,无需启动Spring上下文:
@ExtendWith(MockitoExtension.class) @MockitoSettings public class MyClassTests { @Mock private MyService myService; @InjectMocks private MyClass myClass; @Test public void testDoSomething() { // GIVEN MyEntity myEntity = mock(MyEntity.class); MySubEntity mySubEntity = mock(MySubEntity.class); // 注意:原被测方法返回BigDecimal.ZERO,若misc operations未修改结果,需同步预期值 BigDecimal expectedResult = BigDecimal.ZERO; // WHEN // 使用类型精确的匹配器anyLong(),避免匹配失败 when(myService.getEntity(anyLong())).thenReturn(myEntity); when(myEntity.getSubEntity()).thenReturn(mySubEntity); when(mySubEntity.getId()).thenReturn(42L); BigDecimal actualResult = myClass.doSomething(36L); // THEN assertEquals(expectedResult, actualResult); // 可选:验证方法调用是否符合预期 verify(myService).getEntity(36L); } }
方案2:保留SpringBootTest(需依赖Spring上下文时)
移除@ExtendWith(MockitoExtension.class),改用Spring提供的@MockBean注解创建Mock对象,Spring会自动将Mock注入到上下文的MyClass实例中:
@SpringBootTest public class MyClassTests { @MockBean private MyService myService; @Autowired private MyClass myClass; @Test public void testDoSomething() { // GIVEN MyEntity myEntity = mock(MyEntity.class); MySubEntity mySubEntity = mock(MySubEntity.class); BigDecimal expectedResult = BigDecimal.ZERO; // WHEN when(myService.getEntity(anyLong())).thenReturn(myEntity); when(myEntity.getSubEntity()).thenReturn(mySubEntity); when(mySubEntity.getId()).thenReturn(42L); BigDecimal actualResult = myClass.doSomething(36L); // THEN assertEquals(expectedResult, actualResult); } }
额外注意点
原被测方法doSomething中result初始值为BigDecimal.ZERO,如果misc operations没有修改该值,测试中的预期值需调整为BigDecimal.ZERO,否则断言会失败。
内容的提问来源于stack exchange,提问作者Harkonnen
相关产品推荐
相关产品推荐

