3

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.

Nikolas Charalambidis
  • 40,893
  • 16
  • 117
  • 183
  • 1
    Is it an option for you to use Spring's [AuditingEntityListener](https://docs.spring.io/spring-data/jpa/docs/current/api/org/springframework/data/jpa/domain/support/AuditingEntityListener.html)? Seems like a good fit for your use case. You could then provide an alternative `auditingDateTimeProvider` bean in your test configuration. – wjans Dec 02 '20 at 11:10
  • @wjans: I have never heard about this one, I'll take a look for sure and give it a try. Would you give me a sample usage of this thing (preferably an answer)? – Nikolas Charalambidis Dec 02 '20 at 11:14
  • this link will help you https://stackoverflow.com/questions/24673150/how-to-mock-prepersist-method – priyranjan Dec 02 '20 at 11:38
  • @priyranjan: I stumbled upon this question. It doesn't provide a way to achieve this as long as I use no dependency for setting the `lastModified` value. Moreover, correct me if I am wrong, JMockit is yet another dependency and I am not keen to include it just for one test (I use Spring + jUnit 5 + Mockito combo). – Nikolas Charalambidis Dec 02 '20 at 12:09

2 Answers2

3

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.

wjans
  • 10,009
  • 5
  • 32
  • 43
  • Thanks for the answer. What is the definition and role of `@LastModifiedDate`? For the unit testing itself, I need to create a custom `Clock` used for the auditing configuration, right? – Nikolas Charalambidis Dec 02 '20 at 15:54
  • `@LastModifiedDate` will be updated when updating the entity (`@PreUpdate`), you also have `@CreatedDate` which is similar but happens at creation time only (`@PrePersist`). For testing you could provide a fixed clock which you control. But do note that this is _a_ suggestion, another option could be to expose a mocked bean. – wjans Dec 02 '20 at 16:29
0

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

  1. Mock 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 option
  2. Wrap LocalDateTime.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.

Roger Gustavsson
  • 1,689
  • 10
  • 20
  • It's a very good idea, however, the Mockito spy cannot be persisted: `org.springframework.dao.InvalidDataAccessApiUsageException: Unknown entity: com.mycompant.entity.MyEntity$MockitoMock$1764544448;` – Nikolas Charalambidis Dec 02 '20 at 15:48