How to set @CreatedDate in the past (for testing)
Asked Answered
C

5

15

My spring-data-jpa backend has a class that populates the (test) database with a lot of test data. The class usese the spring data repositories to create entities. All my entities have a field annotated with @CreatedData and the corresponding @EntityListeners(AuditingEntityListener.class) annotation on the model class. This works fine so far. dateCreated is automatically set correctly.

But when running Junit test I sometimes need to create a (test) object with a dateCreated in the past. How can I archive this? Only via plain JDBC?

Coexecutor answered 21/2, 2017 at 17:47 Comment(3)
The (not) quick and dirty way: Thread.sleep(1000)Unmistakable
Related: https://mcmap.net/q/224123/-injecting-a-spring-dependency-into-a-jpa-entitylistener/476716Unmistakable
I need CreatedDate a month in the past :-) Thank you for the link. I already feared that a spring solution would be "large".Coexecutor
C
7

I found a way that works for me (using plain JDBC):

First I create my domain objects for testing with spring-data-jpa:

MyModel savedModel = myRepo.save(myModel);

That automatically fills the "dateCreated" with timestamp of "now". Since I need creation dates in the past for testing I manually tweak them with plain JDBC:

@Autowired
JdbcTemplate jdbcTemplate;

[...]

// This syntax is for H2 DB.  For MySQL you need to use DATE_ADD
String sql = "UPDATE myTable SET created_at = DATEADD('DAY', -"+ageInDays+", NOW()) WHERE id='"+savedLaw.getId()+"'";
jdbcTemplate.execute(sql);
savedModel.setCreatedAt(new Date(System.currentTimeMillis() - ageInDays* 3600*24*1000);

Do not forget to also setCreatedAt inside the returned model class.

Coexecutor answered 21/2, 2017 at 19:48 Comment(0)
C
7

In case you are using Spring Boot, you can mock dateTimeProvider bean used in EnableJpaAuditing annotation. Spring Data uses this bean to obtain current time at the entity creation/modification.

@Import({TestDateTimeProvider.class})
@DataJpaTest
@EnableJpaAuditing(dateTimeProviderRef = "testDateTimeProvider")
public class SomeTest {

    @MockBean
    DateTimeProvider dateTimeProvider;

...

It is necessary to define actual testDateTimeProvider bean, but it won't be used at all, as you will use mock instead. You can write mockito methods afterwards as usual:

@Test
public void shouldUseMockDate() {

    when(dateTimeProvider.getNow()).thenReturn(Optional.of(LocalDateTime.of(2020, 2, 2, 0, 0, 0)));
    
   
   ... actual test assertions ...
Cask answered 21/8, 2020 at 21:44 Comment(0)
B
4

The best and cleanest way I found using Mockito is to change the AuditingHandler before saving by sating your worn implementation of DateTimeProvider. Since Java 8 this can be done using Lambda Interfaces like this:

@SpyBean
private AuditingHandler auditingHandler;

// inside the test
auditingHandler.setDateTimeProvider(() -> Optional.of(YOUR_DATE));
savedEntity = repository.save(entity);
Buttocks answered 29/5, 2023 at 0:25 Comment(0)
G
0

I do not know exact test case which you are using, but i can see few solutions:

  • create a mock object, once the dateCreated is called return date month ago
  • maybe use in-mem db, populate it with date before test
  • go with AOP from the link provided in comments
Glottic answered 21/2, 2017 at 18:19 Comment(1)
Thank you for the suggestions: MockObjects: I need hundrests entries each with different creation dates in my DB. InMemoryDB: That is what I am using. I populate it with my TestDataCreator.java I am creating my testdata in java (instead of plain sql in a data.sql file) because I need to juggle a lot of references between the test data entries.Coexecutor
A
0

I found it easier to just create a AuditingConfig class inside my test folder and import it into test that require it.

In my case, I also needed the AuditorAware bean to provide a user since I am using the @LastModifiedBy and @CreatedBy annotations as well as the date ones.

Here is my AuditingConfig:

// /test/java/com/domain/api/AuditingConfig.class
@Configuration
@EnableJpaAuditing(auditorAwareRef = "auditorProvider")
public class AuditingConfig {

    @Bean
    public AuditorAware<String> auditorProvider() {
        return () -> Optional.of("Test");
    }

    @Bean
    public DateTimeProvider dateTimeProvider() {
        return () -> Optional.of(LocalDateTime.now());
    }
}

You can then import it into your tests like so:

@DataJpaTest
@Import(AuditingConfig.class) // importing auditing config here
public class AddressTypeRepositoryTest {


    @Autowired
    private AddressTypeRepository addressTypeRepository;

    @Test
    public void AddressTypeRepository_Save_ReturnSavedAddressType(){

        //Arrange
        AddressType addressType = AddressType.builder()
                .name("Test").build();
        //Act
        AddressType savedAddressType = addressTypeRepository.save(addressType);
        //Assert
        Assertions.assertThat(savedAddressType).isNotNull();
        Assertions.assertThat(savedAddressType.getAddressTypeId()).isGreaterThan(0);
        Assertions.assertThat(savedAddressType.getCreatedBy()).isNotNull();
        Assertions.assertThat(savedAddressType.getCreatedDate()).isNotNull();
        Assertions.assertThat(savedAddressType.getUpdatedDate()).isNotNull();
        Assertions.assertThat(savedAddressType.getUpdatedBy()).isNotNull();
    }

}
Alvar answered 26/5 at 20:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.