How to configure JPA for testing in Maven

Viewed 60461

Is there a way to set up a second persistence.xml file in a Maven project such that it is used for testing instead of the normal one that is used for deployment?

I tried putting a persistence.xml into src/test/resources/META-INF, which gets copied into target/test-classes/META-INF, but it seems target/classes/META-INF (the copy from the src/main/resources) gets preferred, despite mvn -X test listing the classpath entries in the right order:

[DEBUG] Test Classpath :
[DEBUG]   /home/uqpbecke/dev/NetBeansProjects/UserManager/target/test-classes
[DEBUG]   /home/uqpbecke/dev/NetBeansProjects/UserManager/target/classes
[DEBUG]   /home/uqpbecke/.m2/repository/junit/junit/4.5/junit-4.5.jar
...

I would like to be able to run tests against a simple hsqldb configuration without having to change the deployment version of the JPA configuration, ideally straight after project checkout without any need for local tweaking.

17 Answers

The following will work for Maven 2.1+ (prior to that there wasn't a phase between test and package that you could bind an execution to).

You can use the maven-antrun-plugin to replace the persistence.xml with the test version for the duration of the tests, then restore the proper version before the project is packaged.

This example assumes the production version is src/main/resources/META-INF/persistence.xml and the test version is src/test/resources/META-INF/persistence.xml, so they will be copied to target/classes/META-INF and target/test-classes/META-INF respectively.

It would be more elegant to encapsulate this into a mojo, but as you're only copying a file, it seems like overkill.

<plugin>
  <artifactId>maven-antrun-plugin</artifactId>
  <version>1.3</version>
  <executions>
    <execution>
      <id>copy-test-persistence</id>
      <phase>process-test-resources</phase>
      <configuration>
        <tasks>
          <!--backup the "proper" persistence.xml-->
          <copy file="${project.build.outputDirectory}/META-INF/persistence.xml" tofile="${project.build.outputDirectory}/META-INF/persistence.xml.proper"/>
          <!--replace the "proper" persistence.xml with the "test" version-->
          <copy file="${project.build.testOutputDirectory}/META-INF/persistence.xml" tofile="${project.build.outputDirectory}/META-INF/persistence.xml"/>
        </tasks>
      </configuration>
      <goals>
        <goal>run</goal>
      </goals>
    </execution>
    <execution>
      <id>restore-persistence</id>
      <phase>prepare-package</phase>
      <configuration>
        <tasks>
          <!--restore the "proper" persistence.xml-->
          <copy file="${project.build.outputDirectory}/META-INF/persistence.xml.proper" tofile="${project.build.outputDirectory}/META-INF/persistence.xml"/>
        </tasks>
      </configuration>
      <goals>
        <goal>run</goal>
      </goals>
    </execution>
  </executions>
</plugin>

It seems multiple persistence.xml files is a general problem with JPA, solved only by classloading tricks.

A workaround that works for me is to define multiple persistence units in one persistence.xml file and then make sure that your deployment and test code use a different binding (in Spring you can set the "persistenceUnitName" property on the entity manager factory). It pollutes your deployment file with the test configuration, but if you don't mind that it works ok.

I prefer the solution of using different persistence.xml for testing and production as Rich Seller post (thanks!!).

But need to change:

<copy file="${project.build.outputDirectory}/META-INF/persistence.xml.proper" tofile="${project.build.outputDirectory}/META-INF/persistence.xml"/>

for:

<move file="${project.build.outputDirectory}/META-INF/persistence.xml.proper" tofile="${project.build.outputDirectory}/META-INF/persistence.xml" overwrite="true"/>

In order persistence.xml.proper not embedded in .jar file

Keep two copies of persistence.xml file. One for testing and another for normal build.

The default life cycle copy the build persistence.xml to src/test/resources/META-INF

Create a separate profile which when run will copy the testing persistence.xml to src/test/resources/META-INF

Persistence.xml is used as a starting point to search for entity classes unless you list all classes explicitly and add . So if you want to override this file with another one, say from src/test/resources, you have to specify every single entity class in this second persistence.xml otherwise no entity class would be found.

Another solution would be to overwrite the file using the maven-resources-plugin ('copy-resources' goal). But then you have to overwrite it twice, once for testing (e.g. phase process-test-classes) and once for the real packaging (phase 'prepare-package').

I'm trying to do the same thing. I have a solution that works for me - yours may vary (and you might not love the solution... it's a bit low-level).

I came across an article on the net where they were using a custom class loader to do something similar which served as inspiration. If anyone can see how to improve then suggestions would be welcome btw. For deployment I rely on container injection of the EntityManager but for testing I create it myself using this code:

final Thread currentThread = Thread.currentThread();
final ClassLoader saveClassLoader = currentThread.getContextClassLoader();
currentThread.setContextClassLoader(new ClassLoaderProxy(saveClassLoader));

EntityManagerFactory emFactory = Persistence.createEntityManagerFactory("test");
em = emFactory.createEntityManager();

Then the ClassLoaderProxy is about as minimal as you can get and just redirects requests for META-INF/persistence.xml to META-INF/test-persist.xml:

public class ClassLoaderProxy extends ClassLoader {

    public ClassLoaderProxy(final ClassLoader parent) {
        super();
    }

    @Override
    public Enumeration<URL> getResources(final String name) throws IOException {
        if (!"META-INF/persistence.xml".equals(name)) {
            return super.getResources(name);
        } else {
            System.out.println("Redirecting persistence.xml to test-persist.xml");
            return super.getResources("META-INF/test-persist.xml");
        }
    }
}

Just to explain this a bit more:

  1. There are two persistence.xml files (one named persistence.xml that is used outside testing and one named test-persist.xml that is used for tests).
  2. The custom class loader is only active for unit tests (for deployment everything is normal)
  3. The custom class loader redirects requests for "META-INF/persistence.xml" to the test version ("META-INF/test-persist.xml").

I was originally hitting some problems because Hibernate will revert back (somehow) to the classloader that was used to load Hibernate (at least I think that is what was going on). I've found that putting the ClassLoader switching code (the first block) as a static block in your Test case it will get loaded before Hibernate but that, depending on your unit test structure you may also need to put the same code in other places (yuck).

I found 2 possibilities without changing classloader/using other Maven plugins/profiles/copy-overwrite files.

TL;DR: check provider name.

At first I started to construct the entityManagerFactory programmatically, like here: create entity manager programmatically without persistence file.

So I did sth very similar:

    @BeforeClass
    public static void prepare() {
        Map<String, Object> configOverrides = new HashMap<>();
        configOverrides.put("hibernate.connection.driver_class", "org.h2.Driver");
        configOverrides.put("hibernate.connection.url", "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1");
        configOverrides.put("hibernate.connection.username", "sa");
        configOverrides.put("hibernate.connection.password", "sa");
        configOverrides.put("hibernate.dialect", "org.hibernate.dialect.H2Dialect");
        configOverrides.put("hibernate.show_sql", "true");
        configOverrides.put("hibernate.hbm2ddl.auto", "validate");
        factory = new HibernatePersistence().createContainerEntityManagerFactory(
                new CustomPersistenceUnitInfo(), configOverrides
        );
        //factory = Persistence.createEntityManagerFactory("test");
        assertNotNull(factory);
    }
...
    private static class CustomPersistenceUnitInfo implements PersistenceUnitInfo {

        @Override
        public String getPersistenceUnitName() {
            return "test";
        }

        @Override
        public String getPersistenceProviderClassName() {
            return "org.hibernate.jpa.HibernatePersistenceProvider";
 // <------------note here: this is wrong!
        }

        @Override
        public PersistenceUnitTransactionType getTransactionType() {
            return PersistenceUnitTransactionType.RESOURCE_LOCAL;
        }

        @Override
        public DataSource getJtaDataSource() {
            return null;
        }

        @Override
        public DataSource getNonJtaDataSource() {
            return null;
        }

        @Override
        public List<String> getMappingFileNames() {
            return Collections.emptyList();
        }

        @Override
        public List<URL> getJarFileUrls() {
            try {
                return Collections.list(this.getClass()
                        .getClassLoader()
                        .getResources(""));
            } catch (IOException e) {
                throw new UncheckedIOException(e);
            }
        }

        @Override
        public URL getPersistenceUnitRootUrl() {
            return null;
        }

        @Override
        public List<String> getManagedClassNames() {
            return Arrays.asList(
                    "com.app.Entity1",
                    "com.app.Entity2"
            );
        }

        @Override
        public boolean excludeUnlistedClasses() {
            return true;
        }

        @Override
        public SharedCacheMode getSharedCacheMode() {
            return null;
        }

        @Override
        public ValidationMode getValidationMode() {
            return null;
        }

        @Override
        public Properties getProperties() {
            return null;
        }

        @Override
        public String getPersistenceXMLSchemaVersion() {
            return null;
        }

        @Override
        public ClassLoader getClassLoader() {
            return null;
        }

        @Override
        public void addTransformer(final ClassTransformer classTransformer) {

        }

        @Override
        public ClassLoader getNewTempClassLoader() {
            return null;
        }
    }


But then, I found it still return null. Why?

Then I found that when I use com.hibernate.ejb.HibernatePersistence class, the provider should not be com.hibernate.jpa.HibernatePersistenceProvider, but com.hibernate.ejb.HibernatePersistence. The class HibernatePersistenceProvider is not even found with IDEA "Open Class", even when it is in the main persistence.xml.

In Ejb3Configuration.class I found:

        integration = integration != null ? Collections.unmodifiableMap(integration) : CollectionHelper.EMPTY_MAP;
        String provider = (String)integration.get("javax.persistence.provider");
        if (provider == null) {
            provider = info.getPersistenceProviderClassName();
        }

        if (provider != null && !provider.trim().startsWith(IMPLEMENTATION_NAME)) { // private static final String IMPLEMENTATION_NAME = HibernatePersistence.class.getName(); which, is, "com.hibernate.ejb.HibernatePersistence"
            LOG.requiredDifferentProvider(provider);
            return null;
        } else {

So I went back to the first solution of persistence.xml, and change provider name, and now it works. It seems that even the provider in main is jpa.xxx, in tests it is not.

So, in summary, 3 things to check:

  • turn on -X in Maven to check if maven-resources-plugin really copied your src/test/resources/META-INF/persistence.xml into target/test-classes(I think this never fails)
  • check if hibernate-entitymanager is in your classpath(you can check with mvn dependency:tree -Dincludes=org.hibernate:hibernate-entitymanager.
  • check provider's name, most important one. Should be org.hibernate.ejb.HibernatePersistence.
<persistence version="2.0"
             xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
    <persistence-unit name="test" transaction-type="RESOURCE_LOCAL">
        <provider>org.hibernate.ejb.HibernatePersistence</provider>
        <class>com.app.model.Company</class>
        ...

Related