Как настроить JPA для тестирования в Maven - PullRequest
66 голосов
/ 22 декабря 2008

Есть ли способ настроить второй файл persistence.xml в проекте Maven так, чтобы он использовался для тестирования вместо обычного, используемого для развертывания?

Я попытался поместить файл 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 ]

26 голосов
/ 25 июля 2009

Следующее будет работать для Maven 2.1+ (до этого между тестом и пакетом не было фазы, с которой вы могли бы связать выполнение).

Вы можете использовать maven-antrun-plugin для замены файла persistence.xml тестовой версией на время тестов, а затем восстановить правильную версию перед упаковкой проекта.

В этом примере предполагается, что рабочая версия - это src / main / resources / META-INF / persistence.xml, а тестовая версия - src / test / resources / META-INF / persistence.xml, поэтому они будут скопированы в target / классы / META-INF и целевой / test-классы / 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>
20 голосов
/ 01 июня 2010

В проекте EE6 / CDI / JPA тест src/test/resources/META-INF/persistence.xml выполняется без каких-либо дополнительных настроек.

При использовании JPA в Spring в контексте приложения, используемого для тестирования, работает следующее:

<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 также определяет так называемый " корень единицы сохраняемости ", который затем определяет, какие классы сканируются для аннотаций @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. Вместо этого большинство настроек, таких как драйверы, диалект и дополнительные учетные данные, можно выполнить в контексте Spring. Также такие параметры, как hibernate.hbm2ddl.auto могут быть переданы в контексте :

<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>
13 голосов
/ 22 декабря 2008

Кажется, несколько файлов persistence.xml - это общая проблема с JPA, решаемая только хитростями загрузки классов.

Обходной путь, который мне подходит, - это определить несколько единиц персистентности в одном файле persistence.xml, а затем убедиться, что в вашем коде развертывания и тестирования используется другое связывание (в Spring можно установить свойство «persistenceUnitName» в диспетчере сущностей завод). Он загрязняет ваш файл развертывания тестовой конфигурацией, но если вы не возражаете, он работает нормально.

7 голосов
/ 06 декабря 2015

Добавить файл persistance.xml для тестов: /src/test/resources/META-INF/persistence.xml Как сказал @Arjan, это изменит корень модуля персистентности , и классы сущностей будут сканироваться в целевых / тестовых классах. Чтобы справиться с этим, добавьте элемент jar-file в этот файл persistance.xml:

/ SRC / тест / ресурсы / 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-файл может предназначаться для каталогов, а не только для файлов jar.

6 голосов
/ 31 марта 2011

Я попробовал подход ClassLoaderProxy, но у меня возникла проблема с тем, что аннотированные классы JPA не обрабатываются hibernate как постоянные классы.

Поэтому решили попробовать без использования 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 и вызывают функцию setup () в TestCase.setup ().

Единственный недостаток - поддерживать постоянные классы в актуальном состоянии, но для тестирования JUnit это приемлемо для меня.

5 голосов
/ 11 декабря 2009

Я предпочитаю решение использовать другой файл persistence.xml для тестирования и производства как Богатый продавец 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

3 голосов
/ 04 ноября 2014

Этот ответ может показаться глупым, но я искал способ, который позволил бы мне запустить эти тесты из затмения на Run As -> 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. Это работает.

3 голосов
/ 22 июня 2009

Persistence.xml используется в качестве отправной точки для поиска классов сущностей, если вы явно не перечислите все классы и не добавите их. Поэтому, если вы хотите переопределить этот файл другим, скажем, из src / test / resources, вы должны указать каждый отдельный класс сущностей в этом втором файле persistence.xml, иначе класс сущностей не будет найден.

Другим решением было бы перезаписать файл с помощью плагина maven-resources-plugin (цель 'copy-resources'). Но затем вам придется перезаписать его дважды, один раз для тестирования (например, фазы process-test-classes) и один раз для реальной упаковки (фаза 'prepare-package').

3 голосов
/ 22 декабря 2008

Сохраните две копии файла persistence.xml. Один для тестирования, другой для нормальной сборки.

Жизненный цикл по умолчанию копирует сборку persistence.xml в src / test / resources / META-INF

Создайте отдельный профиль, который при запуске скопирует файл persistence.xml тестирования в src / test / resources / META-INF

1 голос
/ 19 апреля 2016

Еще одним вариантом для этого варианта использования было бы добавление нескольких единиц персистентности, один для, скажем, производства, а другой для тестирования и внедрения 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;
    }
}
...