我有一个简单的实体,要求上次修改时间应该在persist上更新。
@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();
}
}
我需要实现一个作业,该作业可以查询超过某个时间(比如说一天)的实体,修改其状态并持久化它们。我在为包含这种用例的单元测试创建数据时遇到了问题。
尽管我手动设置了< code>lastModified时间,但无论设置值如何,< code>@PrePersist都会导致其发生变化。
@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);
问题:如何准备预持久化数据(lastModified
)而不为了单元测试而大幅修改MyEntity
类?欢迎使用Mockito的解决方案。
注意我使用Spring Boot jUnit 5 Mockito
我尝试过的事情:
>
如何使用Mockito和jUnit模拟持久化和实体:模拟持久化实体不是一个办法,因为我需要将实体持久化在H2中以进行进一步检查。此外,我尝试使用这个技巧Spring Boot#7033使用间谍bean,结果相同。
Hibernate提示:如何为所有实体激活实体侦听器:使用为单元测试范围配置的静态嵌套类< code>@TestConfiguration以编程方式添加侦听器。这东西根本不叫。
@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;
}
}
脏方法:创建一个方法级类,用< code>@PrePersist扩展< code>MyEntity,该类“覆盖”了< code>lastModified值。它会产生< code > org . spring framework . Dao . invaliddataaccessapiusageexception 。为了解决这个问题,这样的实体依赖于< code>@Inheritance注释(JPA : Entity extend with entity),我不想仅仅为了单元测试而使用它。该实体不得在生产代码中扩展。
您可以使用Spring Data JPA AuditingEntityListener。
只需通过@org.springframework.data.jpa.repository.config.EnableJpaAudting
启用它,并可选择提供自定义dateTimeProviderRef
,如下所示:
@Configuration
@EnableJpaAuditing(dateTimeProviderRef = "myAuditingDateTimeProvider")
public class JpaAuditingConfig {
@Bean(name = "myAuditingDateTimeProvider")
public DateTimeProvider dateTimeProvider(Clock clock) {
return () -> Optional.of(now(clock));
}
}
您的实体可能如下所示:
@Data // Lombok thing
@Entity
@Table(name = "MY_ENTITY")
@EntityListeners(AuditingEntityListener.class)
public class MyEntity {
@LastModifiedDate
private LocalDateTime lastModified;
// irrelevant columns including id omitted
}
在上面的例子中,可以通过Spring提供一个< code>java.time.Clock,它已经解决了您关于测试的问题。但是您也可以提供一个专用的测试配置,指定一个不同的/模拟的< code>DateTimeProvider。
请注意,此处提到的解决方案不是纯粹的单元测试方法。但是根据你的问题和你尝试过的事情,我得出结论,使用Spring的解决方案是可行的。
有了Mockito,你也许可以做这样的事情?
MyEntity sut = Mockito.spy(new MyEntity());
Mockito.doNothing().when(sut).initializeUUID();
但我不确定它到底适合你的测试。
另外两个选择
LocalDateTime.now()
,让它返回你想要的值。但是可能持久化进程中的其他代码调用了这个方法,可能不喜欢它。如果是这种情况,转到另一个选项LocalDateTime.now()
在你自己的类中,然后模拟它。可悲的是,这涉及到对你的实体类的微小更改,但只有对LocalDateTime.now()
的调用会被模拟。在这种情况下,我还没有描述如何与Mockito一起嘲笑,因为我不熟悉它。我只用过JMockit。但以上就是原则。