Spring Boot单元测试如何不改动生产代码预置实体lastModified值
JPA实体测试场景问题描述
生产端实体定义
业务要求实体的最后修改时间在持久化时自动更新,当前生产代码的MyEntity类定义如下:
@Data // Lombok thing @Entity @Table(name = "MY_ENTITY") public class MyEntity { @Column(name = "LAST_MODIFIED", nullable = false) private LocalDateTime lastModified; // irrelevant columns including id omitted @PrePersist public void initializeUUID() { lastModified = LocalDateTime.now(); } }
当前业务需求
实现一个定时任务,查询出最后修改时间早于指定阈值(例如1天前)的实体,修改实体状态后再完成持久化。
测试阶段遇到的阻塞问题
编写覆盖该场景的单元测试时发现:即便手动给实体设置了lastModified的值,@PrePersist注解的逻辑也会无视手动设置值,在持久化时将其覆盖为当前时间,根本无法造出符合查询阈值要求的测试数据。
当前测试环境基于Spring Boot Test配置了连接内存H2数据库,仓库实例的注入方式如下:
@Autowired // Spring Boot tests are configured against in-memory H2 database MyEntityRepository myEntityRepository;
原本的测试数据构造逻辑编写如下,实际运行时手动设置的旧时间值根本无法存入数据库:
var entity = new MyEntity(); entity.setLastModified(LocalDateTime.now().minusDays(3)); myEntityRepository.entity(entity);
核心解决目标
完全不为单元测试大幅修改生产代码MyEntity类,基于现有Spring Boot + jUnit 5 + Mockito技术栈,找到可落地的方案,准备好带有指定lastModified旧值的预持久化测试数据。
已尝试的无效方案及遇到的问题
- 全量mock持久层方案:由于测试需要将实体真正持久化到H2数据库以完成后续校验,全mock持久化的方案并不适用;另外也尝试过通过Spring Boot社区提到的spy Bean方案包裹实体,最终@PrePersist覆盖值的问题依然存在。
- 测试作用域配置Hibernate实体监听器:在
@TestConfiguration中编程式注册全局实体监听器,意图在插入前拦截修改lastModified值。配置日志显示监听器已经被注册,但是对应的回调方法完全没有被触发,没有生效。配套的测试配置代码如下:
@TestConfiguration public static class UnitTestConfiguration { // logged as registered @Component public static class MyEntityListener implements PreInsertEventListener { @Override public boolean onPreInsert(PreInsertEvent event) { // not called at all Object entity = event.getEntity(); log.info("HERE {}" + entity); // no log appears // intention to modify the `lastModified` value return true; } } }
- 测试用子类重写@PrePersist方法:写一个方法级别的子类继承MyEntity,重写带@PrePersist注解的方法以覆盖原本的lastModified赋值逻辑,结果直接抛出
org.springframework.dao.InvalidDataAccessApiUsageException。如果要修复该异常,就需要给实体增加@Inheritance注解调整持久化策略,这类改动完全没必要仅为测试场景存在,而且生产代码中不允许实体做这类继承设计,方案不可行。
内容的提问来源于stack exchange,提问作者Nikolas
相关产品推荐
相关产品推荐

