如何配置JPA以在Maven中进行测试

时间:2008-12-22 04:08:45

标签: java testing maven-2 jpa integration-testing

有没有办法在Maven项目中设置第二个persistence.xml文件,以便用于测试而不是用于部署的普通文件?

我尝试将一个persistence.xml放入src / test / resources / META-INF,它被复制到target / test-classes / META-INF中,但它似乎是target / classes / META-INF(来自于src / main / resources)首选,尽管mvn -X test以正确的顺序列出了类路径条目:

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

我希望能够针对简单的hsqldb配置运行测试,而无需更改JPA配置的部署版本,理想情况是在项目结账后直接进行,无需进行本地调整。

16 个答案:

答案 0 :(得分:26)

以下内容适用于Maven 2.1+(在此之前,测试和包之间没有可以绑定执行的阶段)。

您可以使用maven-antrun-plugin在测试期间将persistence.xml替换为测试版本,然后在打包项目之前恢复正确的版本。

此示例假定生产版本为src / main / resources / META-INF / persistence.xml,测试版本为src / test / resources / META-INF / persistence.xml,因此它们将被复制到目标/类/ META-INF和目标/测试类/ META-INF。

将它封装成mojo会更优雅,但是因为你只是复制一个文件,所以看起来有点过分。

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

答案 1 :(得分:20)

在EE6 / CDI / JPA项目中,无需进一步配置即可正确选择测试src/test/resources/META-INF/persistence.xml

在Spring中使用JPA时,以下内容适用于用于测试的应用程序上下文:

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

此处/src/test/resources/META-INF/persistence.xml(已复制到target/test-classes)优先于/src/main/resources/META-INF/persistence.xml(已复制到target/classes)。

不幸的是,persistence.xml文件的位置也决定了所谓的“persistence unit's root”,然后确定扫描哪些类的@Entity注释。因此,使用/src/test/resources/META-INF/persistence.xml将扫描target/test-classes中的类,而不是target/classes中的类(需要测试的类将存在)。

因此,为了进行测试,需要将<class>条目明确添加到persistence.xml,以避免java.lang.IllegalArgumentException: Not an entity: class ...。通过使用不同的文件名(如<class>)可以避免对persistence-TEST.xml条目的需要,并将该文件放在与常规persistence.xml文件完全相同的文件夹中。然后,测试文件夹中的Spring上下文可以引用<property name="persistenceXmlLocation" value="META-INF/persistence-TEST.xml" />,Spring会在src/main中找到它。

作为替代方案,可以使persistence.xml对于实际应用程序和测试保持相同,并且仅在src/main中定义一个。{1}}。大多数配置(如驱动程序,方言和可选凭据)都可以在Spring上下文中完成。此外,hibernate.hbm2ddl.auto等设置可以是passed in the context

<bean id="dataSource"
    class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <!-- For example: com.mysql.jdbc.Driver or org.h2.Driver -->
    <property name="driverClassName" value="#{myConfig['db.driver']}" />
    <!-- For example: jdbc:mysql://localhost:3306/myDbName or 
        jdbc:h2:mem:test;DB_CLOSE_DELAY=-1 -->
    <property name="url" value="#{myConfig['db.url']}" />
    <!-- Ignored for H2 -->
    <property name="username" value="#{myConfig['db.username']}" />
    <property name="password" value="#{myConfig['db.password']}" />
</bean>

<bean id="jpaAdaptor"
    class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
    <!-- For example: org.hibernate.dialect.MySQL5Dialect or 
        org.hibernate.dialect.H2Dialect -->
    <property name="databasePlatform" value="#{myConfig['db.dialect']}" />
</bean>

<bean id="entityManagerFactory"
    class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="jpaVendorAdapter" ref="jpaAdapter" />
    <property name="jpaProperties">
        <props>
            <!-- For example: validate, update, create or create-drop -->
            <prop key="hibernate.hbm2ddl.auto">#{myConfig['db.ddl']}</prop>
            <prop key="hibernate.show_sql">#{myConfig['db.showSql']}</prop>
            <prop key="hibernate.format_sql">true</prop>
        </props>
    </property>
</bean>

答案 2 :(得分:13)

似乎多个persistence.xml文件是JPA的一般问题,只能通过类加载技巧解决。

对我有用的解决方法是在一个persistence.xml文件中定义多个持久性单元,然后确保您的部署和测试代码使用不同的绑定(在Spring中,您可以在实体管理器上设置“persistenceUnitName”属性厂)。它会使用测试配置污染您的部署文件,但如果您不介意它可以正常工作。

答案 3 :(得分:7)

为测试添加persistance.xml:/src/test/resources/META-INF/persistence.xml 正如@Arjan所说,这将改变persistance unit's root,并且将在目标/测试类中扫描实体类。要处理此问题,请将 jar-file 元素添加到此persistance.xml:

/src/test/resources/META-INF/persistence.xml

<persistence 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"
             version="2.0">
    <persistence-unit name="com.some.project">
        <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
        <jar-file>${project.basedir}/target/classes</jar-file>
        <properties>
            <property name="javax.persistence.jdbc.url" value="jdbc:postgresql://localhost:5432/test_database" />
            <property name="javax.persistence.jdbc.driver" value="org.postgresql.Driver" />
            <property name="javax.persistence.jdbc.user" value="user" />
            <property name="javax.persistence.jdbc.password" value="..." />
        </properties>
    </persistence-unit>
</persistence>

然后,将测试资源的过滤添加到您的pom.xml:

<project>
    ...
    <build>
        ...
        <testResources>
            <testResource>
                <directory>src/test/resources</directory>
                <filtering>true</filtering>
            </testResource>
        </testResources>
        ...
    </build>
...
</project>

这样可行,因为 jar-file 可以定位到目录,而不仅仅是jar文件。

答案 4 :(得分:6)

我尝试了ClassLoaderProxy方法,但遇到的问题是hibernate不会将JPA带注释的类作为持久化类处理。

所以决定不使用persistence.xml就试一试。优点是maven构建和Eclipse JUnit测试无需修改即可运行。

我有一个持久的JUnit测试支持类。

public class PersistenceTestSupport {

    protected EntityManager em;
    protected EntityTransaction et;

    /**
     * Setup the the {@code EntityManager} and {@code EntityTransaction} for
     * local junit testing.
     */
    public void setup() {

        Properties props = new Properties();
        props.put("hibernate.hbm2ddl.auto", "create-drop");
        props.put("hibernate.dialect", "org.hibernate.dialect.MySQLDialect");
        props.put("hibernate.connection.url", "jdbc:mysql://localhost/db_name");
        props.put("hibernate.connection.driver_class", "com.mysql.jdbc.Driver");
        props.put("hibernate.connection.username", "user");
        props.put("hibernate.connection.password", "****");

        Ejb3Configuration cfg = new Ejb3Configuration();
        em = cfg.addProperties(props)
            .addAnnotatedClass(Class1.class)
            .addAnnotatedClass(Class2.class)
            ...
                    .addAnnotatedClass(Classn.class)
            .buildEntityManagerFactory()
            .createEntityManager();

        et = em.getTransaction();
    }
}

我的测试类只是扩展PersistenceTestSupport并调用TestCase.setup()中的setup()。

唯一的缺点是要保持持久化类,但对于JUnit测试,这对我来说是可以接受的。

答案 5 :(得分:5)

我更喜欢使用不同的persistence.xml作为Rich Seller post进行测试和制作的解决方案(谢谢!!)。

但需要改变:

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

有:

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

为了使persistence.xml.proper不嵌入.jar文件

答案 6 :(得分:3)

保留persistence.xml文件的两个副本。一个用于测试,另一个用于正常构建。

默认生命周期将构建persistence.xml复制到src / test / resources / META-INF

创建一个单独的配置文件,在运行时将测试persistence.xml复制到src / test / resources / META-INF

答案 7 :(得分:3)

Persistence.xml用作搜索实体类的起点,除非您明确列出所有类并添加。 因此,如果你想用另一个文件覆盖这个文件,比如src / test / resources,你必须在第二个persistence.xml中指定每个实体类,否则不会找到任何实体类。

另一个解决方案是使用maven-resources-plugin('copy-resources'目标)覆盖文件。但是你必须覆盖它两次,一次用于测试(例如阶段过程测试类),一次用于真正的包装(阶段'准备包')。

答案 8 :(得分:3)

这个答案可能听起来很愚蠢,但我正在寻找一种方法让我通过Run As - &gt;从eclipse运行这些测试。 JUnit Test。这就是我的成就:

@BeforeClass
public static void setUp() throws IOException {
    Files.copy(new File("target/test-classes/META-INF/persistence.xml"), new File("target/classes/META-INF/persistence.xml"));
    // ...
}

我只是将test / persistence.xml复制到classes / persistence.xml。这有效。

答案 9 :(得分:1)

我正在尝试做同样的事情。我有一个适合我的解决方案 - 你的可能会有所不同(你可能不喜欢这个解决方案......它有点低级)。

我在网上看到一篇文章,他们使用自定义类加载器来做类似的事情,作为灵感。如果有人能看到如何改进那么建议将是受欢迎的顺便说一句。对于部署,我依赖于EntityManager的容器注入,但是为了测试我自己使用以下代码创建它:

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

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

然后ClassLoaderProxy大约尽可能少,只需将META-INF / persistence.xml的请求重定向到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");
        }
    }
}

再解释一下:

  1. 有两个persistence.xml文件(一个名为persistence.xml,用于测试之外,另一个名为test-persist.xml,用于测试)。
  2. 自定义类加载器处于活动状态以进行单元测试(部署一切正常)
  3. 自定义类加载器将对“META-INF / persistence.xml”的请求重定向到测试版本(“META-INF / test-persist.xml”)。
  4. 我最初遇到了一些问题,因为Hibernate将(以某种方式)恢复到用于加载Hibernate的类加载器(至少我认为这是正在发生的事情)。我已经发现将ClassLoader切换代码(第一个块)作为静态块放在你的测试用例中它将在Hibernate之前加载但是,根据你的单元测试结构,你可能还需要在其他地方放置相同的代码(呸)。

答案 10 :(得分:1)

另一种方法是使用单独的persistence.xml进行测试(测试/../ META-INF / persistence.xml,但重写扫描器如下: -

测试persistence.xml需要包含

<property name="hibernate.ejb.resource_scanner" value = "...TestScanner" />

新类TestScanner的代码如下。

import java.lang.annotation.Annotation;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Set;
import org.hibernate.ejb.packaging.NamedInputStream;
import org.hibernate.ejb.packaging.NativeScanner;


public class TestScanner extends NativeScanner
{

@Override
public Set <Class <?> > 
getClassesInJar (URL jar, Set <Class <? extends Annotation> > annotations)
{  return super.getClassesInJar (getUpdatedURL (jar), annotations); }

@Override
public Set <NamedInputStream> 
getFilesInJar (URL jar, Set <String> patterns)
{  return super.getFilesInJar (getUpdatedURL (jar), patterns); }

@Override
public Set <Package> 
getPackagesInJar (URL jar, Set <Class <? extends Annotation> > annotations)
{  return super.getPackagesInJar (getUpdatedURL (jar), annotations); }

private URL getUpdatedURL (URL url)
{
  String oldURL = url.toExternalForm ();
  String newURL = oldURL.replaceAll ("test-classes", "classes");
  URL result;
  try {
    result = newURL.equals (oldURL) ? url : new URL (newURL);
  } catch (MalformedURLException e)
  {  // Whatever  }
  return result;
}

}

答案 11 :(得分:1)

使用OpenEJB时,可以使用备用描述符覆盖persistence.xml:http://tomee.apache.org/alternate-descriptors.html

答案 12 :(得分:1)

此用例的另一个选项是添加多个持久性单元,一个用于生产,另一个用于测试并相应地注入EntityManagerFactory。

将两个持久性单元放入实际项目的persistence.xml中,并让测试用例注入正确的EntityManager。下面的例子说明了如何用guice做到这一点。请注意,为了完整性,我已经留下了一些模拟嘲弄,模拟特定代码已经相应标记,并且不需要注射。

public class HibernateTestDatabaseProvider extends AbstractModule {
    private static final ThreadLocal<EntityManager> ENTITYMANAGER_CACHE = new ThreadLocal<>();

    @Override
    public void configure() {
    }

    @Provides
    @Singleton
    public EntityManagerFactory provideEntityManagerFactory() {
        return Persistence.createEntityManagerFactory("my.test.persistence.unit");
    }

    @Provides
    public CriteriaBuilder provideCriteriaBuilder(EntityManagerFactory entityManagerFactory) {
        return entityManagerFactory.getCriteriaBuilder();
    }

    @Provides
    public EntityManager provideEntityManager(EntityManagerFactory entityManagerFactory) {
        EntityManager entityManager = ENTITYMANAGER_CACHE.get();
        if (entityManager == null) {
            // prevent commits on the database, requires mockito. Not relevant for this answer
            entityManager = spy(entityManagerFactory.createEntityManager());


            EntityTransaction et = spy(entityManager.getTransaction());
            when(entityManager.getTransaction()).thenReturn(et);
            doNothing().when(et).commit();

            ENTITYMANAGER_CACHE.set(entityManager);
        }
        return entityManager;
    }
}

答案 13 :(得分:0)

使用persistence.xml将测试放在自己的maven项目中

答案 14 :(得分:0)

我建议使用不同的maven配置文件,您可以过滤data.proprerties文件,每个配置文件都有一个database.properties。

这样您就不必保留除.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>

在单击测试的确定性和集成测试失败的帮助下,你已经完成了。

    <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>2.12</version>
    <configuration>
        <junitArtifactName>org.junit:com.springsource.org.junit</junitArtifactName>
        <!--see: https://issuetracker.springsource.com/browse/EBR-220-->
        <printSummary>false</printSummary>
        <redirectTestOutputToFile>true</redirectTestOutputToFile>
        <!-- Skips unit tests if the value of skip.unit.tests property is true -->
        <skipTests>${skip.unit.tests}</skipTests>
        <!-- Excludes integration tests when unit tests are run. -->
        <excludes>
            <exclude>${integration.test.files}</exclude>
        </excludes>
    </configuration>
</plugin>


<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-failsafe-plugin</artifactId>
    <version>2.12</version>
    <configuration>
        <!-- Skips integration tests if the value of skip.integration.tests property is true -->
        <skipTests>${skip.integration.tests}</skipTests>
        <includes>
            <include>${integration.test.files}</include>
        </includes>
        <forkMode>once</forkMode>
        <!--
                            <reuseForks>false</reuseForks>
                            <forkCount>1</forkCount>
        -->
    </configuration>
    <executions>
        <execution>
            <id>integration-test</id>
            <goals>
                <goal>integration-test</goal>
            </goals>
        </execution>
        <execution>
            <id>verify</id>
            <goals>
                <goal>verify</goal>
            </goals>
        </execution>
    </executions>
</plugin>

现在,您只需要mvn test进行单元测试,mvn verify -Pintegration进行集成测试。 显然你应该在指定的(在配置文件中)路径上创建database.properties文件(或其他地方并更改路径)

基于参考:http://www.petrikainulainen.net/programming/tips-and-tricks/creating-profile-specific-configuration-files-with-maven/

答案 15 :(得分:0)

这是Rich Seller的答案的扩展,正确处理Hibernate在类路径上找到多个persistence.xml文件并预先测试状态恢复。

设定:

为部署/打包创建一个持久性文件,为测试创建一个:

  • 的src /主/资源/ persistence.xml中

  • 的src /的测试 /资源/ persistence-的测试 .XML

你的pom.xml中的

将它添加到插件部分:

        <plugin>
            <artifactId>maven-antrun-plugin</artifactId>
            <version>1.3</version>
            <executions>
                <execution>
                    <id>copy-test-persistence</id>
                    <phase>process-test-resources</phase>
                    <configuration>
                        <tasks>
                            <echo>renaming deployment persistence.xml</echo>
                            <move file="${project.build.outputDirectory}/META-INF/persistence.xml" tofile="${project.build.outputDirectory}/META-INF/persistence.xml.proper"/>
                            <echo>replacing deployment persistence.xml with test version</echo>
                            <copy file="${project.build.testOutputDirectory}/META-INF/persistence-testing.xml" tofile="${project.build.outputDirectory}/META-INF/persistence.xml" overwrite="true"/>
                        </tasks>
                    </configuration>
                    <goals>
                        <goal>run</goal>
                    </goals>
                </execution>
                <execution>
                    <id>restore-persistence</id>
                    <phase>prepare-package</phase>
                    <configuration>
                        <tasks>
                            <echo>restoring the deployment persistence.xml</echo>
                            <move file="${project.build.outputDirectory}/META-INF/persistence.xml.proper" tofile="${project.build.outputDirectory}/META-INF/persistence.xml" overwrite="true"/>
                        </tasks>
                    </configuration>
                    <goals>
                        <goal>run</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>

优于其他解决方案

  • 无需额外的Java代码
  • 类路径上只有一个persistence.xml
  • 构建和测试都按预期工作
  • 描述控制台上的输出(回声)
  • 对于包装,状态是100%恢复。没有剩余的文件