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

JUnit单元测试通过但无代码覆盖率问题排查(Mockito)

问题背景

为局部更新(Patch调用)的服务方法编写单元测试时,出现测试执行通过但代码覆盖率统计异常的问题:测试可以覆盖被测方法内部调用的子方法,但被测方法本身的代码无法被覆盖率工具统计。

原有测试实现
@MockitoSettings(strictness = Strictness.LENIENT)
@Test
public void updateProductPartialTest() throws Exception {
    Product prod = saveprod();
    ProductDto dto = saveDto();
    PartialProductDto updto = patchDto();
    Product upprod = upprod();
    ProductDto updtoo = updateDto();
    
    String id = "5e7e2fcf-c0b8-4c9b-860b-55d71a3c580f";
    given(mapper.ProductDtotoProduct(dto)).willReturn(prod);
    given(mapper.ProductToProductDto(prod)).willReturn(dto);
    given(productrepository.findByProductId(dto.getProductId())).willReturn(Optional.empty());
    given(productrepository.save(prod)).willReturn(prod);
    service.addProduct(dto);
    given(productrepository.findByProductId(dto.getProductId())).willReturn(Optional.of(prod));

    given(service.mappingForPatrialUpdate(updto, prod)).willReturn(upprod);
    
    Mockito.doReturn(updtoo).when(service).updateProductPartial(updto, id);
    given(mapper.ProductToProductDto(prod)).willReturn(dto);
    given(mapper.ProductDtotoProduct(dto)).willReturn(prod);
    given(service.addProduct(dto)).willReturn(dto);
    service.addProduct(dto);
    service.updateProductPartial(updto, id);
    given(productrepository.findByProductId(id)).willReturn(Optional.of(upprod));
    assertThat(productrepository.findByProductId(id).get().equals(upprod));
    
}
测试Mock配置
@Mock
ProductRepository productrepository;
@Mock
SimpleProductdtoProductMapper mapper;

@Spy
@InjectMocks
ProductServiceImpl service;
@InjectMocks
ProductGetServiceImpl serviceget;
问题根因

判断完全准确:问题由Mockito.doReturn(updtoo).when(service).updateProductPartial(updto, id);这行代码导致。
当前service实例使用@Spy注解修饰为间谍对象,这行代码直接对被测方法本身做了桩设置,后续调用service.updateProductPartial(updto, id)时,Mockito会直接返回预设的桩值updtoo,完全不会执行方法内部的真实业务逻辑,覆盖率工具自然无法采集到该方法的执行数据。
除此之外原有测试还存在两个影响覆盖率和测试有效性的问题:

  • 存在大量冗余的重复桩配置,甚至对非本次测试目标的addProduct方法也做了不必要的桩化和调用,干扰测试逻辑
  • 断言写法无效:assertThat(productrepository.findByProductId(id).get().equals(upprod));仅传入了一个boolean值,没有做真正的结果校验,且调用的是Mock出来的repository方法,校验无实际意义
修复方案
  • 核心修改:删除对被测方法updateProductPartial的桩化代码,保证调用该方法时执行真实业务逻辑。
  • 桩规则仅保留被测方法内部依赖的外部组件、需要隔离的内部子方法的配置,不需要桩化被测方法本身,也不需要保留和当前测试目标无关的addProduct相关调用与桩配置。
  • 修正断言逻辑:直接对被测方法的真实返回值做校验,必要时可通过verify验证内部依赖的调用是否符合预期。

修正后的参考测试代码如下:

@MockitoSettings(strictness = Strictness.LENIENT)
@Test
public void updateProductPartialTest() throws Exception {
    // 初始化测试数据
    Product existingProd = saveprod();
    PartialProductDto patchDto = patchDto();
    Product updatedProd = upprod();
    ProductDto expectedResult = updateDto();
    String targetId = "5e7e2fcf-c0b8-4c9b-860b-55d71a3c580f";

    // 仅桩化被测方法的外部依赖、需要隔离的内部子方法
    given(productrepository.findByProductId(targetId)).willReturn(Optional.of(existingProd));
    given(service.mappingForPatrialUpdate(patchDto, existingProd)).willReturn(updatedProd);
    given(productrepository.save(updatedProd)).willReturn(updatedProd);
    given(mapper.ProductToProductDto(updatedProd)).willReturn(expectedResult);

    // 调用真实被测方法
    ProductDto actualResult = service.updateProductPartial(patchDto, targetId);

    // 校验结果与交互逻辑
    assertThat(actualResult).isEqualTo(expectedResult);
    verify(productrepository).findByProductId(targetId);
    verify(service).mappingForPatrialUpdate(patchDto, existingProd);
    verify(productrepository).save(updatedProd);
}

注意:使用@Spy修饰被测服务实例时,遵循「桩化所有外部依赖、不桩化被测方法本身」的原则即可。对于被测类内部不需要隔离的子方法(比如简单的参数校验、格式转换逻辑),不需要额外桩化,直接走真实逻辑反而能统计到更完整的覆盖率。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 06:51:21