1

I have a Spring/JPA web application that I would like to write some tests for. Ideally I would like to be able to:

  • create the test DB schema (from the JPA annotated classes) once before the tests are run
  • run each test method in it's own transaction which is rolled back when the test completes
  • specify the (DbUnit) dataset to be loaded for each test either at the per-class or per-method level. The test data should be loaded after the transaction has started so that the test data will also be rolled back when the test completes
  • inject Spring beans into the test class

I'm aware that Spring provides classes which can provide the transactional behaviour I'm seeking. Ideally, the final solution will look something like this

// This dataset will be used for all tests that don't override it with their own annotation
@TestData('/dbunit/dataSetDefault.xml')
public class MyTests extends ProbablySomethingFromTheSpringFramework {

  @Test
  void testWithDefaultDataSet() {
    // Transaction is implicitly started here
    // My test code goes here
    // A transaction is implicitly rolled-back here    
  }

  @TestData('/dbunit/dataSetCustom.xml')
  @Test
  void testWithCustomDataSet() {
    // Same as the other test
  }
}

Obviously the parent class and the @TestData are fictitious, is there something available that provides the functionality I'm looking for?

This leaves the question of how to create the test DB schema. Ideally, this would happen once before all the tests are run (by Maven). Can someone suggest how I might achieve this? I imagine it involves using something to convert the JPA annotations to DDL, then something else to load that into the test database schema.

Thanks!

4

2 回答 2

4

Ideally I would like to be able to:

  • create the test DB schema (from the JPA annotated classes) once before the tests are run

At least Hibernate makes it possible to create the database from the annotated classes, I'd imagine other JPA-implementations would work too.

  • run each test method in it's own transaction which is rolled back when the test completes

See @TransactionConfiguration and the defaultRollback -value there, and AbstractTransactionalJUnit4SpringContextTests (there are also similar abstract classes for at least JUnit 3.8 and TestNG), take a good look at the See also-sections in the javadocs, they point to many very useful related classes and annotations.

  • specify the (DbUnit) dataset to be loaded for each test either at the per-class or per-method level. The test data should be loaded after the transaction has started so that the test data will also be rolled back when the test completes

I haven't actually ever used DbUnit, but at least with JUnit, you can use @Before and @BeforeClass to run methods before each test and class, respectively (there's also @After and @AfterClass). If you have a class hierarchy, the @Before/@BeforeClass -annotated methods are run in extension order (baseclass first). For running sql-scripts, see for example SimpleJdbcTestUtils.

  • inject Spring beans into the test class

AbstractTransactionalJUnit4SpringContextTests is ApplicationContextAware, also see @ContextConfiguration for setting things up.

Finally, here's a bit stripped down baseclass I use to extend my actual integration tests from (Spring 3, JUnit4, Hibernate as JPA-provider, if it matters):

    //test-context, same as normal context, except uses H2 for in-memory database and has some stuff for faking session- and request-scope
    @ContextConfiguration(locations="classpath:applicationContext-test.xml") 
    @TransactionConfiguration(transactionManager="txManager", defaultRollback=true)
    @Transactional
    public abstract class IntegrationTestBase extends AbstractTransactionalJUnit4SpringContextTests
    {
        @PersistenceContext
        protected EntityManager em; 

        @Autowired
        protected SomeService serviceAvailableToSubclasses;

        @Before
        public void beforeEachTest()
        {
            //fill database with testdata and whatever you need to, runs before each test in extending classes
        }

        @After
        public void afterEachTest()
        {
            //Do something, if you need to, or just remove this
        }

    }

Extending from this, you can use @Transactional, @Autowired etc. in your deriving classes, or derive more specific abstract test-baseclasses (I have for example IntegrationSessionTestBase and IntegrationSessionNewRequestPerTestBase for different kinds of tests, needing new sessions and/or requests per test).

于 2011-03-24T18:16:44.240 回答
1

我已经用一个简单的基于 JPA (Hibernate) 的应用程序做到了这一点。

<project
  xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

  <modelVersion>4.0.0</modelVersion>

  <groupId>com.soebes.casestudy</groupId>
  <artifactId>casestudy</artifactId>
  <packaging>jar</packaging>
  <version>0.0.1-SNAPSHOT</version>

  <name>Case Study Pizza Ordering</name>
  <url>Pizza Ordering</url>

  <properties>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

    <hibernate-core-version>3.4.0.GA</hibernate-core-version>
    <database.driverClassName>com.mysql.jdbc.Driver</database.driverClassName>
    <database.url>jdbc:mysql://localhost:3306/casestudy</database.url>
    <database.dialect>org.hibernate.dialect.MySQLDialect</database.dialect>
    <database.root.user>root</database.root.user>
    <database.root.password>root</database.root.password>

    <database.user>casestudy</database.user>
    <database.password>casestudy</database.password>

    <database.database>casestudy</database.database>
  </properties>

  <build>
    <resources>
      <resource>
        <directory>${basedir}/src/main/resources</directory>
        <filtering>true</filtering>
      </resource>
    </resources>
    <plugins>
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>hibernate3-maven-plugin</artifactId>
        <version>2.2</version>
        <executions>
          <execution>
            <id>hibernate-create-schema</id>
            <phase>generate-test-sources</phase>
            <goals>
              <goal>hbm2ddl</goal>
            </goals>
            <configuration>
              <components>
                <component>
                  <name>hbm2ddl</name>
                  <implementation>annotationconfiguration</implementation>
                </component>
              </components>
              <componentProperties>
                <configurationfile>/src/main/resources/hibernate.cfg.xml</configurationfile>
                <jdk5>true</jdk5>
                <packagename>com.soebes.casestudy.bo</packagename>
                <console>false</console>
                <outputfilename>create.sql</outputfilename>
                <drop>false</drop>
                <create>true</create>
                <update>false</update>
                <export>false</export>
                <format>true</format>
              </componentProperties>
            </configuration>
          </execution>
          <execution>
            <id>hibernate-drop-schema</id>
            <phase>generate-test-sources</phase>
            <goals>
              <goal>hbm2ddl</goal>
            </goals>
            <configuration>
              <components>
                <component>
                  <name>hbm2ddl</name>
                  <implementation>annotationconfiguration</implementation>
                </component>
              </components>
              <componentProperties>
                <configurationfile>/src/main/resources/hibernate.cfg.xml</configurationfile>
                <jdk5>true</jdk5>
                <packagename>com.soebes.casestudy.bo</packagename>
                <console>false</console>
                <outputfilename>drop.sql</outputfilename>
                <drop>true</drop>
                <create>false</create>
                <update>false</update>
                <export>false</export>
                <format>true</format>
              </componentProperties>
            </configuration>
          </execution>
        </executions>
        <dependencies>
          <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.9</version>
          </dependency>
        </dependencies>
      </plugin>

      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>2.8</version>
      </plugin>

     <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>sql-maven-plugin</artifactId>
        <version>1.4</version>

        <dependencies>
          <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.9</version>
          </dependency>
        </dependencies>

        <!-- common configuration shared by all executions -->
        <configuration>
          <driver>${database.driverClassName}</driver>
          <url>${database.url}</url>
          <username>${database.root.user}</username>
          <password>${database.root.password}</password>
        </configuration>
        <executions>
          <execution>
            <id>drop-database</id>
            <phase>generate-test-resources</phase>
            <goals>
              <goal>execute</goal>
            </goals>
            <configuration>
              <sqlCommand>
              DROP DATABASE IF EXISTS casestudy;
              CREATE DATABASE casestudy;
              GRANT ALL ON casestudy.* TO ${database.user} IDENTIFIED BY '${database.password}';
              </sqlCommand>
            </configuration>
          </execution>
          <execution>
            <id>create-database</id>
            <phase>generate-test-resources</phase>
            <goals>
              <goal>execute</goal>
            </goals>
            <configuration>
              <sqlCommand>
              USE casestudy;
              </sqlCommand>
              <srcFiles>
                <srcFile>${project.build.directory}/hibernate3/sql/create.sql</srcFile>
              </srcFiles>
            </configuration>
          </execution>
        </executions>
      </plugin>

    </plugins>
  </build>

  <dependencies>
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-annotations</artifactId>
      <version>${hibernate-core-version}</version>
    </dependency>
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-core</artifactId>
      <version>3.3.0.SP1</version>
    </dependency>
    <dependency>
        <groupId>javassist</groupId>
        <artifactId>javassist</artifactId>
        <version>3.4.GA</version>
    </dependency>

    <dependency>
      <groupId>org.testng</groupId>
      <artifactId>testng</artifactId>
      <version>5.14.6</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.9</version>
    </dependency>
    <dependency>
      <groupId>log4j</groupId>
      <artifactId>log4j</artifactId>
      <version>1.2.13</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-api</artifactId>
      <version>1.6.1</version>
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-log4j12</artifactId>
      <version>1.6.1</version>
    </dependency>
  </dependencies>
</project>
于 2011-03-24T17:14:31.930 回答