#HOWTO: JPA integration tests with Java EE

Writing good unit tests for your central business logic is essential for the speed of your development and a confident deployment to production. But there are also parts of your application where plain unit tests with mocking frameworks like Mockito aren’t that useful or result in a “mocking-hell”. The interaction of your application with your database is one of this parts where unit tests can’t reproduce the real behavior of this external system. For unit testing your business logic, it’s totally fine to mock the EntityManager and its result but if you want to write tests for your JPA models, JPQL queries or the general interaction with the database, you should consider writing integration tests. In this blog post I’ll show a simple way to write JPA integration tests for Java EE applications.

To bootstrap the Java EE application I am using my Java EE 8 archetype:

mvn archetype:generate -DarchetypeGroupId=de.rieckpil.archetypes \
    -DarchetypeArtifactId=javaee8 \
    -DarchetypeVersion=1.0.1 \
    -DgroupId=de.rieckpil.blog \
    -DartifactId=javaee-8-microservice \

As the integration tests don’t run within the application server to improve the speed of these integration tests, you have to add the concrete JPA reference implementation to your project. In addition, I am also using an embedded H2 database to spin off an in-memory database during the tests:

<project xmlns="http://maven.apache.org/POM/4.0.0"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

All of the additional dependencies are marked with the scope test , so they won’t be part of your .war  and the application will stay lean.

For connection to the in-memory, you need to add a persistence unit to your persistence.xmlwhich can be stored in /src/main/test/resources/META-INF. As these test don’t rely on a running application server, the transaction type has to be RESOURCE_LOCAL as we don’t have JTA and have to manually start and commit the transactions:

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.2"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd">
    <persistence-unit name="integration-test" transaction-type="RESOURCE_LOCAL">
            <property name="javax.persistence.jdbc.url"
                      value="jdbc:h2:mem:test:sample;DB_CLOSE_ON_EXIT=FALSE;" />
            <property name="javax.persistence.jdbc.driver" value="org.h2.Driver" />
            <property name="javax.persistence.schema-generation.database.action" value="drop-and-create" />
            <property name="eclipselink.logging.level" value="FINE"/>

To demonstrate a quick example I am using drop-and-create as the schema generation action for the integration tests, but you can also use Flyway or Liquibase to apply your database schema for your integration tests.

A simple JPA entity may look like the following:

public class Customer {

    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String firstName;
    private String lastName;
    private LocalDate dayOfBirth;

    // getter, setter & constructors

For a convenient interaction, I created a JUnit TestRule (kudos to @AdamBien for his test course) which acts as the EntityManager provider for every test. With this rule, you can create the EntityManager from a given unit name and interact with the EntityManager or the EntityTransaction:

public class EntityManagerProvider implements TestRule {

    private EntityManager em;
    private EntityTransaction tx;

    private EntityManagerProvider(String unitName) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory(unitName);
        this.em = emf.createEntityManager();
        this.tx = em.getTransaction();

    public static EntityManagerProvider withUnit(String unitName) {
        return new EntityManagerProvider(unitName);

    public void begin() {

    public void commit() {

    public EntityTransaction tx() {
        return this.tx;

    public EntityManager em() {
        return this.em;

    public Statement apply(Statement base, Description description) {
        return new Statement() {

            public void evaluate() throws Throwable {



A rudimentary integration test might test the right creation of the primary key with @GeneratedValue. Therefore you need to start the database transaction manually and insert some JPA entities. Afterwards querying for all entities in the database should result in a list of entities which all have a primary key applied.

public class CustomerIntegrationTest {

    public EntityManagerProvider provider = EntityManagerProvider.withUnit("integration-test");

    public void testSavingNewCustomer() {
        this.provider.em().persist(new Customer("John", "Duke", LocalDate.of(2000, 12, 12)));
        this.provider.em().persist(new Customer("Foo", "Bar", LocalDate.of(2000, 12, 12)));
        this.provider.em().persist(new Customer("Paul", "One", LocalDate.of(2000, 12, 12)));

        List<Customer> resultList = this.provider.em()
            .createQuery("SELECT c FROM Customer c", Customer.class)

        assertEquals(3, resultList.size());

        for (Customer resultCustomer : resultList) {



This example is just a simple use case to show you an easy way to write integration tests for your database layer. This approach can be also used to test the correct table representation of your JPA entities and more complex queries.

You can find a running example on GitHub.

Have fun with integration testing your applications,



    1. rieckpil October 22, 2018 at 6:39 pm

      Not yet, thanks for the hint Juan 🙂

    2. Rafael Pestano October 23, 2018 at 1:58 pm

      Hi, nice post, you can also use database-rider (most examples use JPA) to help preparing the state of the database for testing: https://github.com/database-rider/database-rider.

      I also like to use Arquillian persistence extension to have tests closer to production (e.g same trasaction model, db config etc…)

Leave a comment

Your email address will not be published. Required fields are marked *