Reputation: 44398
I have a simple entity with a requirement that last modified time should be updated on 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();
}
}
I have a requirement to implement a job that queries such entities older than a certain time (let's say a day), modifies its state and persists them. I have a problem with data creation for an unit test that covers such use case.
Although I set manually lastModified
time, the @PrePersist
causes its change regardless the set value.
@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);
Question: How to prepare pre-persisted data (lastModified
) without drastically modifying the MyEntity
class just for sake of unit tests? A solution using Mockito is welcome.
Note I use Spring Boot + jUnit 5 + Mockito
Things I have tried:
How to mock persisting and Entity with Mockito and jUnit: Mocking persisting the entity is not a way to go because I need the entity to be persisted in H2 for further checks. Moreover, I tried to use spy bean using this trick Spring Boot #7033 with the same result.
Hibernate Tips: How to activate an entity listener for all entities: Adding listener programatically using static nested class configured @TestConfiguration
for the unit test scope. The thing is not called at all.
@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;
}
}
Dirty way: Create a method-level class extending MyEntity
with @PrePersist
that "overrides" the lastModified
value. It results in org.springframework.dao.InvalidDataAccessApiUsageException
. To fix it, such entity relies on the @Inheritance
annotation (JPA : Entity extend with entity), which I don't want to use just for sake of unit tests. The entity must not be extended in the production code.
Upvotes: 3
Views: 3740
Reputation: 1709
With Mockito you could perhaps do something like this?
MyEntity sut = Mockito.spy(new MyEntity());
Mockito.doNothing().when(sut).initializeUUID();
But I'm not sure exactly where it would fit in your tests.
Two other options
LocalDateTime.now()
and let it return the value you want. But maybe other code in the persist process calls this method and may not like it. If that is the case, over to the other optionLocalDateTime.now()
in your own class with a static method and mock that instead. Sadly involves minor changes to your entity class, but only that call to LocalDateTime.now()
will be mocked.I haven't described how to mock with Mockito in this case because I'm not familiar with it. I've only used JMockit. But the above would be the principle.
Upvotes: 0
Reputation: 10115
You could use the Spring Data JPA AuditingEntityListener.
Simply enable it via @org.springframework.data.jpa.repository.config.EnableJpaAuditing
and optionally provide a custom dateTimeProviderRef
like this:
@Configuration
@EnableJpaAuditing(dateTimeProviderRef = "myAuditingDateTimeProvider")
public class JpaAuditingConfig {
@Bean(name = "myAuditingDateTimeProvider")
public DateTimeProvider dateTimeProvider(Clock clock) {
return () -> Optional.of(now(clock));
}
}
Your entity could look something like this then:
@Data // Lombok thing
@Entity
@Table(name = "MY_ENTITY")
@EntityListeners(AuditingEntityListener.class)
public class MyEntity {
@LastModifiedDate
private LocalDateTime lastModified;
// irrelevant columns including id omitted
}
In the above example a java.time.Clock
can be provided via Spring which could already solve your question regarding testing. But you could also provide a dedicated test config specifying a different/mocked DateTimeProvider
.
Please note that the mentioned solution here is not a pure unit test approach. But based on your question and the things you've tried, I concluded that a solution using Spring would be feasible.
Upvotes: 3