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
相关产品推荐
相关产品推荐

