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

被测类中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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 22:44:51