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:

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.

14 Answers
14

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.

Indeed, it is a neat solution. However, one might also want to add the overwite="true" attribute to the last Ant task, to ensure that the proper XML file is copied back. In my environment, it seems to fail due to timestamps being the same for the destination and the target.
–
Vineet ReynoldsJun 22 '10 at 15:10

The clearest answer, in addition to work with a ctrl+c ctrl+v.
–
CafféSep 22 '14 at 17:39

In an EE6/CDI/JPA project, a test src/test/resources/META-INF/persistence.xml is picked up just fine without any further configuration.

When using JPA in Spring, the following works in the application context used for testing:

<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource" />
<!--
JPA requires META-INF/persistence.xml, but somehow prefers the one
in classes/META-INF over the one in test-classes/META-INF. Spring
to the rescue, as it allows for setting things differently, like by
referring to "classpath:persistence-TEST.xml". Or, simply referring
to "META-INF/persistence.xml" makes JPA use the test version too:
-->
<property name="persistenceXmlLocation" value="META-INF/persistence.xml" />
<!-- As defined in /src/test/resources/META-INF/persistence.xml -->
<property name="persistenceUnitName" value="myTestPersistenceUnit" />
<property name="jpaVendorAdapter">
<bean
class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
</bean>
</property>
</bean>

Here, /src/test/resources/META-INF/persistence.xml (copied into target/test-classes) would be preferred over /src/main/resources/META-INF/persistence.xml (copied into target/classes).

Unfortunately, the location of the persistence.xml file also determines the so-called "persistence unit's root", which then determines which classes are scanned for @Entity annotations. So, using /src/test/resources/META-INF/persistence.xml would scan classes in target/test-classes, not classes in target/classes (where the classes that need to be tested would live).

Hence, for testing, one would need to explicitly add <class> entries to persistence.xml, to avoid java.lang.IllegalArgumentException: Not an entity: class .... The need for <class> entries can be avoided by using a different file name, like persistence-TEST.xml, and put that file in the very same folder as the regular persistence.xml file. The Spring context from your test folder can then just refer to <property name="persistenceXmlLocation" value="META-INF/persistence-TEST.xml" />, and Spring will find it for you in src/main.

As an alternative, one might be able to keep persistence.xml the same for the actual application and the tests, and only define one in src/main. Most configuration such as the drivers, dialect and optional credentials can be done in the Spring context instead. Also settings such as hibernate.hbm2ddl.auto can be passed in the context:

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.

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

Nice. Just for posterity: when using this in an EE6/CDI/JPA project (where no trickery is needed at all), JPA would still prefer target/test-classes/META-INF/persistence.xml over target/classes/META-INF/persistence.xml, hence JPA would only scan the classes in target/test-classes, not the ones in target/classes (which might be troublesome when relying on @Entity annotations).
–
ArjanNov 4 '14 at 18:49

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:

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

The custom class loader is only active for unit tests (for deployment everything is normal)

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

Interesting... It'll work, but you'll get some funny looks from other developers. Also, you forgot to restore the thread's context class loader.
–
Joshua DavisNov 1 '11 at 22:41

Fair enough. With respect to the 'forgot' comment... This was some years ago so who knows whether I forgot or not but I can't see how the intention could have been clearer. Adding code (with a try/finally and so on) seems like it would be distracting to the purpose but each to their own I suppose.
–
macbutchMay 23 '12 at 0:34

Agreed about code clarity. I was just imaging someone copy-pasting that code and getting into trouble. Although, if you want to stay out of trouble, best not copy-paste random code. :)
–
Joshua DavisMay 24 '12 at 20:25

I'd suggest using different maven profiles where you could filter your database.proprerties files and have one database.properties per profile.

This way you don't have to keep duplicates of any other configuration files except for the .properties.

<properties>
<!-- Used to locate the profile specific configuration file. -->
<build.profile.id>default</build.profile.id>
<!-- Only unit tests are run by default. -->
<skip.integration.tests>true</skip.integration.tests>
<skip.unit.tests>false</skip.unit.tests>
<integration.test.files>**/*IT.java</integration.test.files>
</properties>
<profiles>
<profile>
<id>default</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<!--
Specifies the build profile id, which is used to find out the correct properties file.
This is not actually necessary for this example, but it can be used for other purposes.
-->
<build.profile.id>default</build.profile.id>
<skip.integration.tests>true</skip.integration.tests>
<skip.unit.tests>false</skip.unit.tests>
</properties>
<build>
<filters>
<!--
Specifies path to the properties file, which contains profile specific
configuration. In this case, the configuration file should be the default spring/database.properties file
-->
<filter>src/main/resources/META-INF/spring/database.properties</filter>
</filters>
<resources>
<!--
Placeholders found from files located in the configured resource directories are replaced
with values found from the profile specific configuration files.
-->
<resource>
<filtering>true</filtering>
<directory>src/main/resources</directory>
<!--
You can also include only specific files found from the configured directory or
exclude files. This can be done by uncommenting following sections and adding
the configuration under includes and excludes tags.
-->
<!--
<includes>
<include></include>
</includes>
<excludes>
<exclude></exclude>
</excludes>
-->
</resource>
</resources>
</build>
</profile>
<profile>
<id>integration</id>
<properties>
<!--
Specifies the build profile id, which is used to find out the correct properties file.
This is not actually necessary for this example, but it can be used for other purposes.
-->
<build.profile.id>integration</build.profile.id>
<skip.integration.tests>false</skip.integration.tests>
<skip.unit.tests>true</skip.unit.tests>
</properties>
<build>
<filters>
<!--
Specifies path to the properties file, which contains profile specific
configuration. In this case, the configuration file is searched
from spring/profiles/it/ directory.
-->
<filter>src/main/resources/META-INF/spring/profiles/${build.profile.id}/database.properties</filter>
</filters>
<resources>
<!--
Placeholders found from files located in the configured resource directories are replaced
with values found from the profile specific configuration files.
-->
<resource>
<filtering>true</filtering>
<directory>src/main/resources</directory>
<!--
You can also include only specific files found from the configured directory or
exclude files. This can be done by uncommenting following sections and adding
the configuration under includes and excludes tags.
-->
<!--
<includes>
<include></include>
</includes>
<excludes>
<exclude></exclude>
</excludes>
-->
</resource>
</resources>
</build>
</profile>
</profiles>

With the help of surefire for unit tests and failsfe for integration tests, you're done.

Now you need just mvn test for your unit tests and mvn verify -Pintegration for your integration tests.
Obviously you should create the database.properties files in the specified (on the profiles) paths (or elsewhere and change the paths)