How to fake last modified time for unit tests? - java

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.

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.

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
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
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.

Related

How do I prevent data conflicts in integration test using JUnit, Spring Boot and TestContainers?

I'm Using Junit in Spring Boot, along with TestContainers (Docker, MySQL 8.0.29) to develop integration tests.
When I execute my tests individually, they all succeed. However when I run them all at once (i.e. in CI/CD), they fail. This is because the tests are not executed in order, and an item might already be deleted before the test to find the item is executed.
To fix this I want to give the entities a unique ID. However, the ID is already automaticly set in my Hibernate entity:
#Entity
public class Assignment {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
I've tried to delete all items before each test is executed, however this does not work:
#Autowired
private JdbcTemplate jdbcTemplate;
#BeforeEach
void tearDown() {
JdbcTestUtils.deleteFromTables(jdbcTemplate, "assignment");
}
Example integration test:
#Test
void When_getById_Verify_Fields() {
AssignmentDTO assignmentDTO = new AssignmentDTO();
assignmentDTO.setTitle("test");
assignmentDTO.setDescription("test");
assignmentDTO.setUserId("1");
assignmentDTO.setCreator("1");
assignmentService.addAssignment(assignmentDTO);
AssignmentDTO expectedAssignment = assignmentService.getById(1);
assertEquals(assignmentDTO.getTitle(), expectedAssignment.getTitle());
assertEquals(assignmentDTO.getDescription(), expectedAssignment.getDescription());
assertEquals(assignmentDTO.getUserId(), expectedAssignment.getUserId());
assertEquals(assignmentDTO.getCreator(), expectedAssignment.getCreator());
}
Each test method should generally remove the data it creates to prevent this problem.
What you can do that the data is not committed to the database you can add the #Transactional annotation to the test method or the test class if you want it for all test methods..
This will do a rollback after the test method.
If you want to execute SQL script before or after each test, you can use annotations from org.springframework.test.context.jdbc.
And an example of how to use it in a test :
#SqlGroup({
#Sql(executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD, scripts = {
"classpath:datasets/integration/integration_test_before.sql"}),
#Sql(executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD, scripts = {
"classpath:datasets/integration/integration_test_after.sql"})})
These annotations need to be added to the class test. With this annotation, you will execute a script before the test and after to initialize data and delete data.
The SQL files need to be in src/test/resources/datasets/integration.
You can find the link to a guide to use it: https://www.baeldung.com/spring-boot-data-sql-and-schema-sql
You can easily fix this by returning the object saved at addAssignment or the id. See my suggestion below
AssignmentDTO assignment = assignmentService.addAssignment(assignmentDTO);
AssignmentDTO expectedAssignment = assignmentService.getById(assignment.getId());
The reason why is failing besides you delete all data is because the auto-increment still persist. See this in order to reset the auto-increment.

Spring Boot Data JPA doing lazy loading - not on a relation but on the loaded entity?

I have just come upon something that I can't describe in any other way than bizarre.
I have a service that is supposed to do this:
it gets passed an external identifier of a customer
it looks up the customer's internal ID
then loads and returns the customer
I'm using optionals as there is a potential chance that external identifiers can't be resolved.
#Transactional(readOnly = true)
public Optional<Customer> getCustomerByExternalReference(String externalId, ReferenceContext referenceContext) {
return externalIdMappingService.resolve(externalId, referenceContext, InternalEntityType.CUSTOMER)
.map(x->new CustomerId(x.getTarget()))
.map(customerRepository::getById);
}
what's noteworthy is here is that: externalIdMappingRepository.resolve returns an Optional<ExternalIdReference> object. If that is present, I attempt to map it to a customer that I then look up from the database. customerRepository is a regular spring data JPA repository (source code below)
However, when trying to access properties from Customer outside the service, I get an exception like this:
org.hibernate.LazyInitializationException: could not initialize proxy [Customer#Customer$CustomerId#3e] - no Session
at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:176)
at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:322)
at org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor.intercept(ByteBuddyInterceptor.java:45)
at org.hibernate.proxy.ProxyConfiguration$InterceptorDispatcher.intercept(ProxyConfiguration.java:95)
at Customer$HibernateProxy$R0X59vMR.getIdName(Unknown Source)
at CustomerApiModel.<init>(CustomerApiModel.java:27)
I understand that this means, that Hibernate decided to lazy load that entity. Once outside the transactional boundaries of the service, it's not able to load the data for that object anymore.
My Question is: Why does Hibernate/Spring Data try a lazy fetching strategy when I essentially just load a specific object by ID from a Spring Data Repository and how I can disable this behaviour the right way.
I'm aware that there is a couple of workarounds to fix the problem (such as allowing hibernate to open sessions at will, or to access properties of that object inside the service). I'm not after such fixes. I want to understand the issue and want to ensure that lazy fetching only happens when it's supposed to happen
Here's the code for customer (just the part that I think is helpful)
#Entity
#Table(name="customer")
#Getter
public class Customer {
#EmbeddedId
private CustomerId id;
#Embeddable
#NoArgsConstructor
#AllArgsConstructor
#EqualsAndHashCode
public static class CustomerId implements Serializable {
private long id;
public long asLong() {
return id;
}
}
}
and here's the source code of the repository:
public interface CustomerRepository extends Repository<Customer, CustomerId> {
List<Customer> findAll();
Customer getById(CustomerId id);
Optional<Customer> findOneById(CustomerId id);
Optional<Customer> findOneByIdName(String idName);
}
By declaring the method Customer getById(CustomerId id); in your CustomerRepository interface, you chose to let your repostory selectively expose the corresponding method with the same signature from the standard spring-data repository methods, as explained by the Repository java doc:
Domain repositories extending this interface can selectively expose CRUD methods by simply declaring methods of the same signature as those declared in CrudRepository.
Different to what the doc says, this also includes methods from JpaRepository.
In the case of Customer getById(CustomerId id);, you therefore invoke the JpaRepository method with the same signature: T getOne(ID id);, which only invokes EntityManager#getReference , as suggested by it's doc:
[...] Returns a reference to the entity with the given identifier. Depending on how the JPA persistence provider is implemented this is very likely to always return an instance and throw an {#link javax.persistence.EntityNotFoundException} on first access. Some of them will reject invalid identifiers immediately. [...]
#see EntityManager#getReference(Class, Object) for details on when an exception is thrown.
When calling EntityManager#getReference, Hibernate first returns a non-initialized proxy of the Entity without executing any SQL statement at all, which is why your method only returns the non-initialized entity.
To fix this, you could change your service logic as follows:
#Transactional(readOnly = true)
public Optional<Customer> getCustomerByExternalReference(String externalId, ReferenceContext referenceContext) {
return externalIdMappingService.resolve(externalId, referenceContext, InternalEntityType.CUSTOMER)
.map(x->new CustomerId(x.getTarget()))
.map(id -> customerRepository.findOneById(id).get()); // <-- changed call
}
This way, spring-data would invoke CrudRepository#findById, which would internally call EntityManager#find and therefore return an initialized entity (or an empty Optional if none was found in the DB).
Related:
When use getOne and findOne methods Spring Data JPA
Why "findById()" returns proxy after calling getOne() on same entity? (attention when using getOne and findById in the same transaction)

LazyInitializationException : Why can't Hibernate create a session on lazy loading?

My project use to have a lot of #Transactional method everywhere. Now because of business logic I do not want to rollback when I have an issue, but want to set my object to an error status (aka saved in the db, so definitly not rollback), so I removed a few #Transactional to start.
Now the issue is where there is a lazy loading there is no session, thus spawning a LazyInitializationException.
Now here are my following trouble-shooting and solution seeking so far :
We're using annotation configuration, so no xml configuration here.
For each action using the database, an EntityManager (defined as an attribute and #Autowired in the service) is created and then deleted (I can clearly see it in the logs when adding the configuration to see them), which apparently is normal according to the Spring documentation.
Using #PersistenceContext or #PersistenceUnit, either with a EntityManagerFactory or with EntityManager doesn't work.
I can load the lazy-loaded attribute I want to use with Hibernate.initialize(), and it then doesn't spawn a LazyInitializationException.
Now my question is : Why can't hibernate do it by itself ? It's seems trivial to me that if I'm using a lazy loading, I want Hibernate to create a session (which he seems perfectly able to do when doing Hibernate.initialize()) to load the date automatically.
Would there be a way to spawn a new entity manager to be use inside my method so Hibernate doesn't create and recreate one all the time ? I really feel like I'm missing something basic about Hibernate, lazy loading and sessions that makes this whole mess a lot less complicated.
Here is an example :
#Entity
#Table(name = "tata")
public class Tata {
#Id
#Column(name = "tata_id")
private Long id;
// getter / setter etc
}
#Entity
#Table(name = "toto")
public class Toto {
#Id
#Column(name = "toto_id")
private Long id;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "tata_id")
private Tata tata;
// getter / setter etc
}
#Service("totoManager")
public class TotoManager extends GenericManagerImpl {
#Autowired
private EntityManager entityManager;
#Autowired
private TotoRepository totoRepository;
public void doSomethingWithTotos() throws XDExceptionImpl {
List<Toto> totos = this.totoRepository.findAll();
for (toto toto : totos) {
// LazyInitializationException here
LOGGER.info("tata : " + toto.getTata().getId());
}
}
}
Hibernate can do it by itself. With setting property hibernate.enable_lazy_load_no_trans=true (for spring boot it should be spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true) you can load any lazy property when transaction is closed. This approach has huge drawback: each time you load lazy property, hibernate opens session and creates transaction in background.
I would recommed fetch lazy properties by entityGraphs. So you doesnt have to move persistent context do upper level or change fetch type in your entities.
Try to read something about Open session in view to understand why Lazy loading does not work out of session/transaction. If you want you can set property spring.jpa.open-in-view=true and it will load your lazy loaded data.

Calculated Property wrt JPA. Does this work in my case?

I have a scenario where I have 2 labels that need to be configured. The names of the labels are 'Out Date' and 'In Date'. I only have one field in the database called 'Date'. Whether it is 'Out' or 'In' is decided at the runtime by the value of an Enum 'Scenario'. However, I need to actually show the user Out Date&In Date so that he can select 1 or both of them. I heard that calculated field concept it JPA will assist in this. Is this true or is there some other way that I can achieve this. Below is some sample code.
Date
#Override
#Convert("DateTimeConverter")
#Column(name = "DATE")
public DateTime getDate() {
return date;
}
Scenario
#Override
#Convert("EnumConverter")
#Column(name = "SCENARIO")
public Scenario getScenario() {
return scenario;
}
Scenario is any enum with the values OUT(1),IN(2)
There are no calculated properties in JPA.
You can use #Transient annotation to create properties that are not persisted but calculated based on other fields:
#Transient
public DateTime getInDate() {
if (scenario == Scenario.IN) {
return date;
}
return null;
}
#Transient
public DateTime getOutDate() {
if (scenario == Scenario.OUT) {
return date;
}
return null;
}
Alternatively, if you are using Hibernate you can use proprietary annotation #Formula:
#Formula("case when SCENARIO = 2 then DATE else NULL end")
#Convert("DateTimeConverter")
private DateTime inDate;
#Formula("case when SCENARIO = 1 then DATE else NULL end")
#Convert("DateTimeConverter")
private DateTime outDate;
I prefer the first option because:
it is easier to test with unit tests
it is easier to use the entity in unit tests
it does not require proprietary extensions
generally there might be some problems with portability of SQL, although in this problem case when is SQL 92 compatible so it does not apply here
The only problem I can is is that in simplest approach is that we abandon encapsulation by exposing to clients internals of the entity (scenario and date properties). But you can always hide these properties with accessor protected, JPA will still handle that.
To compute properties within JPA entities, you can use JPA callbacks.
See this Hibernate JPA Callbacks documentation. (Note: JPA callbacks are not specific to hibernate, it's part of latest JPA 2.1 specification).
And also this OpenJpa JPA Calbacks one.
Following entity life-cycle categories have a Pre and Post event which can be intercepted by the entity manager to invoke methods:
Persist -> #PrePersist, #PostPersist
Remove -> #PreRemove, #PostRemove
Update -> #PreUpdate, #PostUpdate
Load -> #PostLoad (No Pre for this ...)
So let's say you want to compute a complexLabel label from two persisted entity fields label1 and label2 in an entity titled MyEntity:
#Entity
public class MyEntity {
private String label1;
private String label2;
#Transient
private String complexLabel;
#PostLoad
#PostUpdate // See EDIT
// ...
public void computeComplexLabel(){
complexLabel = label1 + "::" + label2;
}
}
As #Dawid wrote, you have to annotate complexLabel with #Transient in order to make them ignored by persistence. If you don't do this, persistence fails because there is no such column in MyEntity corresponding table.
With #PostLoad annotation, computeComplexLabel() method is called by entity manager just after the loading of any instance of MyEntity from persistence.
Thus, #PostLoad annotated method is best suited to put your post loading entity properties enhancement code.
Bellow is an extract from JPA 2.1 specification about PostLoad:
The PostLoad method for an entity is invoked after the entity has been
loaded into the current persistence context from the database or
after the refresh operation has been applied to it. The PostLoad
method is invoked before a query result is returned or accessed or
before an association is traversed.
EDIT
As pointed out by #Dawid, you could also use #PostUpdate in case you want to compute this transient field just after the entity update, and use other callbacks when needed.

Dynamic schema in Hibernate #Table Annotation

Imagine you have four MySQL database schemas across two environments:
foo (the prod db),
bar (the in-progress restructuring of the foo db),
foo_beta (the test db),
and bar_beta (the test db for new structures).
Further, imagine you have a Spring Boot app with Hibernate annotations on the entities, like so:
#Table(name="customer", schema="bar")
public class Customer { ... }
#Table(name="customer", schema="foo")
public class LegacyCustomer { ... }
When developing locally it's no problem. You mimic the production database table names in your local environment. But then you try to demo functionality before it goes live and want to upload it to the server. You start another instance of the app on another port and realize this copy needs to point to "foo_beta" and "bar_beta", not "foo" and "bar"! What to do!
Were you using only one schema in your app, you could've left off the schema all-together and specified hibernate.default_schema, but... you're using two. So that's out.
Spring EL--e.g. #Table(name="customer", schema="${myApp.schemaName}") isn't an option--(with even some snooty "no-one needs this" comments), so if dynamically defining schemas is absurd, what does one do? Other than, you know, not getting into this ridiculous scenario in the first place.
I have fixed such kind of problem by adding support for my own schema annotation to Hibernate. It is not very hard to implement by extending LocalSessionFactoryBean (or AnnotationSessionFactoryBean for Hibernate 3). The annotation looks like this
#Target(TYPE)
#Retention(RUNTIME)
public #interface Schema {
String alias() default "";
String group() default "";
}
Example of using
#Entity
#Table
#Schema(alias = "em", group = "ref")
public class SomePersistent {
}
And a schema name for every combination of alias and group is specified in a spring configuration.
you can try with interceptors
public class CustomInterceptor extends EmptyInterceptor {
#Override
public String onPrepareStatement(String sql) {
String prepedStatement = super.onPrepareStatement(sql);
prepedStatement = prepedStatement.replaceAll("schema", "Schema1");
return prepedStatement;
}
}
add this interceptor in session object as
Session session = sessionFactory.withOptions().interceptor(new MyInterceptor()).openSession();
so what happens is when ever onPrepareStatement is executed this block of code will be called and schema name will be changed from schema to schema1.
You can override the settings you declare in the annotations using a orm.xml file. Configure maven or whatever you use to generate your deployable build artifacts to create that override file for the test environment.

Categories