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

Spring Boot服务测试AssertionFailedError 返回值为0问题排查

问题描述

开发了简易shampoos-warehouse api后,对服务层逻辑做测试时运行异常,测试代码如下:

@RunWith(SpringRunner.class)
@SpringBootTest
class ShampooServiceTest {

    public static final String SHAMPOO_NAME = "name";
    public static final int SHAMPOO_CAPACITY = 1000;
    public static final int SHAMPOO_QUANTITY = 30;
    public static final boolean SHAMPOO_IS_HEALTHY = true;
    public static final LocalDate SHAMPOO_EXPIRATION_DATE = LocalDate.parse("2022-11-11");

    public static final String SHAMPOO_PARAM = "equals";


    @Autowired
    ShampooService shampooService;

    @MockBean
    ShampooRepository shampooRepository;

    @Test
    void getAddAndGetTotalShampoosByParams(){
        shampooService.addShampoos(UUID.randomUUID(), SHAMPOO_NAME, SHAMPOO_CAPACITY, SHAMPOO_QUANTITY, SHAMPOO_IS_HEALTHY, SHAMPOO_EXPIRATION_DATE);
        assertEquals(String.valueOf(SHAMPOO_QUANTITY), shampooService.getTotalShampoosByParam(SHAMPOO_NAME, SHAMPOO_CAPACITY, SHAMPOO_PARAM));
    }
}

测试执行抛出断言错误:

org.opentest4j.AssertionFailedError: 
Expected :30
Actual   :0

项目对应ShampooDto代码:

public class ShampooDto {

public interface IncomeValidation{}
public interface OutcomeValidation{}

@NotNull(groups={IncomeValidation.class, OutcomeValidation.class})
private String name;

@Min(100)
@Max(5000)
@NotNull(groups={IncomeValidation.class, OutcomeValidation.class})
private int capacity;

@NotNull(groups={IncomeValidation.class, OutcomeValidation.class})
private int quantity;

@NotNull(groups={IncomeValidation.class})
private boolean healthy;

@NotNull(groups={IncomeValidation.class})
private LocalDate expiration_date;
}

对应Service层实现代码:

public String getTotalShampoosByParam(String name, int capacity, String param){
    Integer totalQuantity;
    switch (param){
        case "less":
            totalQuantity = shampooRepository.getTotalQuantityWhereCapacityLess(name, capacity);
            break;
        case "equals":
            totalQuantity = shampooRepository.getTotalQuantityWhereCapacityEquals(name, capacity);
            break;
        case "more":
            totalQuantity = shampooRepository.getTotalQuantityWhereCapacityMore(name, capacity);
            break;
        default:
            totalQuantity = 0;
    }
    if(totalQuantity!=null){
        return String.valueOf(totalQuantity);
    }
    else{
        return "0";
    }

}

public Shampoo addShampoos(UUID id, String name, int capacity, int quantity, boolean healthy, LocalDate expiration_date) throws ValidationException {
    Optional<Shampoo> answer = shampooRepository.findShampooByNameAndCapacity(name, capacity);
    if(answer.isPresent()){
        Shampoo shampoo = answer.get();
        shampoo.setQuantity(shampoo.getQuantity() + quantity);
        shampooRepository.save(shampoo);
        return shampoo;
    }
    else{
        Shampoo shampoo = new Shampoo(id, name, capacity, quantity, healthy, expiration_date, LocalDate.now());
        shampooRepository.save(shampoo);
        return shampoo;
    }
}

已知现象:通过接口请求发起新增Shampoos、按参数查询总量的操作均可正常运行,但执行上述单元测试时预期返回30,实际返回0。

问题根因

核心问题是测试类中使用@MockBean标注了ShampooRepository,被Mock替换的Repository是假对象,默认所有方法调用都会返回类型对应的默认空值,不会真的执行数据存储、查询逻辑:

  • 调用addShampoos时,Mock的findShampooByNameAndCapacity默认返回Optional.empty(),代码会走新建Shampoo后调用save的分支,但Mock对象的save是空实现,不会真的保存数据
  • 后续调用getTotalShampoosByParam时,Mock的getTotalQuantityWhereCapacityEquals默认返回null,方法最终走空值分支返回"0",和预期值不符
    接口运行正常是因为线上运行时注入的是真实的Repository实现,会真正操作数据库,不存在Mock对象默认返回空值的问题。
修复方案

根据测试目的二选一即可:

  • 做纯服务层单元测试(不依赖真实数据库):给Mock的Repository配置打桩规则,定义方法调用的预期返回值
    示例代码如下,在测试方法中添加Mock规则:
    // 模拟首次新增时,名称+容量查询无已有数据
    when(shampooRepository.findShampooByNameAndCapacity(SHAMPOO_NAME, SHAMPOO_CAPACITY))
            .thenReturn(Optional.empty());
    // 捕获save方法传入的Shampoo对象,模拟save逻辑返回存入的对象
    ArgumentCaptor<Shampoo> shampooCaptor = ArgumentCaptor.forClass(Shampoo.class);
    when(shampooRepository.save(shampooCaptor.capture())).thenAnswer(inv -> inv.getArgument(0));
    // 模拟equals查询时,返回存入对象的实际库存值
    when(shampooRepository.getTotalQuantityWhereCapacityEquals(SHAMPOO_NAME, SHAMPOO_CAPACITY))
            .thenAnswer(inv -> shampooCaptor.getValue().getQuantity());
    
  • 做集成测试(走真实数据库交互逻辑):去掉ShampooRepository上的@MockBean注解,配合H2等内存测试数据库启动测试上下文,此时会注入真实Repository实现,数据会真正写入测试库、查询也会返回真实结果。

另外注意原测试类代码中RunWith(SpringRunner.class)漏了开头的@注解符号,需要补全为@RunWith(SpringRunner.class),否则Spring测试上下文不会正常加载。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 01:01:43