When you have JPA specific logic or a complex object graph, you surely want to test JPA without worrying of testing your whole application (i.e you just need a plain entity manager and no container).
To do so, you can of course create your entity manager factory, then you create an entity manager and play with it. But it can be nice, in particular with Java 8, to write a small JUnit rule to simply this kind of testing.
This rule would take in charge:
– a default configuration
– the ability to configure the persistent unit
– the ability to run a task in a transaction
– the ability to use the entitymanager without worrying of its creation
Create a JUnit rule
To create a JUnit rule you need to implement `org.junit.rules.TestRule`.
public class OpenJPARule implements TestRule {
@Override
public Statement apply(Statement base, Description description) {
return new Statement() {
@Override
public void evaluate() throws Throwable {
base.evaluate();
}
};
}
This implementation does nothing, it simply delegates to the original execution. Yet, we now see that we can wrap `base.evaluate()` in a try/finally block and add some code before and after the execution.
That’s what we’ll do.
Instead of using standard `Persistence` factory class to create the `EntityManagedFactory`, we’ll use OpenJPA on (`org.apache.openjpa.persistence.OpenJPAPersistence`) because it doesn’t need a persistence.xml to exist.
Our implementation will then look like:
OpenJPAEntityManagerFactory emf = OpenJPAPersistence.createEntityManagerFactory("test", null, config);
OpenJPAEntityManager em = emf.createEntityManager();
try {
base.evaluate();
} finally {
em.close();
emf.close();
}
This is great but we can’t do much with this `EntityManager` while it is not accessible to the application.
To keep the ability to run tests in parallel, we’ll store it in a thread local in the rule:
package com.github.rmannibucau.openjpa.java8.junit;
import org.apache.openjpa.persistence.OpenJPAEntityManager;
import org.apache.openjpa.persistence.OpenJPAEntityManagerFactory;
import org.apache.openjpa.persistence.OpenJPAPersistence;
import org.hsqldb.jdbcDriver;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import static java.util.Arrays.asList;
public class OpenJPARule implements TestRule {
private final ThreadLocal<EntityManager> em = new ThreadLocal<>();
@Override
public Statement apply(Statement base, Description description) {
return new Statement() {
@Override
public void evaluate() throws Throwable {
OpenJPAEntityManagerFactory emf = OpenJPAPersistence.createEntityManagerFactory("test", null, config);
OpenJPAEntityManager em = emf.createEntityManager();
OpenJPARule.this.em.set(em);
try {
base.evaluate();
} finally {
em.close();
emf.close();
OpenJPARule.this.em.remove();
}
}
};
}
}
By making the thread local accessible, we’ll be able to use it in the application. But before we’ll need
an entity manager which can be created. That’s to say we need some configuration.
For the testing purpose we’ll use hsqldb as testing database (h2 would be perfect as well) and we’ll use a default
configuration already wiring a datasource, forcing to create the schema and supporting unenhanced classes.
Note: this last point is used for small tests but it is not recommanded to count on it in a real application (with relationships).
Here is our default configuration:
Map config = new HashMap() {{
put("javax.persistence.jdbc.driver", jdbcDriver.class.getName());
put("javax.persistence.jdbc.url", "jdbc:hsqldb:mem:test");
put("javax.persistence.jdbc.user", "sa");
put("javax.persistence.jdbc.password", "");
put("openjpa.RuntimeUnenhancedClasses", "supported");
put("openjpa.jdbc.SynchronizeMappings", "buildSchema(ForeignKeys=true)");
put("openjpa.InitializeEagerly", "true");
}};
We’ll store this configuration in our rule, allowing us to customize it through a configure method. We’ll also add a particular
method that enables to specify which types are taken into account (entities). This is done by setting `openjpa.MetaDataFactory`
property giving a value to `types` key:
public class OpenJPARule implements TestRule {
private final ThreadLocal<EntityManager> em = new ThreadLocal<>();
private final Map config = new HashMap() {{
put("javax.persistence.jdbc.driver", jdbcDriver.class.getName());
put("javax.persistence.jdbc.url", "jdbc:hsqldb:mem:test");
put("javax.persistence.jdbc.user", "sa");
put("javax.persistence.jdbc.password", "");
put("openjpa.RuntimeUnenhancedClasses", "supported");
put("openjpa.jdbc.SynchronizeMappings", "buildSchema(ForeignKeys=true)");
put("openjpa.InitializeEagerly", "true");
}};
public OpenJPARule configure(final String key, final String value) {
config.put(key, value);
return this;
}
public OpenJPARule types(final Class<?>... classes) {
configure("openjpa.MetaDataFactory", "types=" + asList(classes).stream().map(Class::getName).collect(Collectors.joining(",")));
return this;
}
@Override
public Statement apply(Statement base, Description description) {
// ...
}
}
Now, to miss a way to use the rule and `EntityManager`, we’ll provide two methods:
– run: execute a task getting the entity manager as parameter. `java.util.function.Consumer` is a perfect parameter for that
– transaction: execute a task in a transaction. Here, returning some values (persisted entity?) is nice, so we’ll use `java.util.function.Function` as parameter
Finally these methods will look like:
public void run(Consumer<EntityManager> task) {
task.accept(em.get());
}
public <T> T transaction(Function<EntityManager, T> task) {
EntityManager entityManager = em.get();
EntityTransaction tx = entityManager.getTransaction();
tx.begin();
boolean cancelled = false;
try {
return task.apply(entityManager);
} catch (RuntimeException e) {
cancelled = true;
tx.rollback();
throw e;
} finally {
if (!cancelled) {
tx.commit();
}
}
}
What is nice in using `Function` and `Consumer` is that it is lambda friendly.
Use the OpenJPA JUnit rule
Now we have all the code, we can use our rule in test class:
public class JPATest {
@Rule
public final OpenJPARule $ = new OpenJPARule()
.types(DatedEntity.class)
.configure("openjpa.Log", "SQL=TRACE");
@Test
public void doPersist() {
final AnEntity de = new AnEntity();
$.transaction((em) -> {
em.persist(de);
return de;
});
$.run(EntityManager::clear);
}
}
Like this:
Like Loading...