开发者

Spring: unit and integration tests

I'm looking for best practices for setting up unit and integration tests using Spring.

I usually use 3 kind of tests:

  • "real" unit tests (no dependencies)
  • tests run either as "unit" test (in-memory db, local calls, mock objects,...) or as integration test (persistent db, remote calls,...)
  • tests run only as integration tests

Currently I only have tests of the second category, which is the tricky part. I set-up a base test class like:

@ContextConfiguration(locations = { "/my_spring_test.xml" })
public abstract class AbstractMyTestCase extends AbstractJUnit4SpringContextTests

And "unit" tests like:

public class FooTest extends AbstractMyTestCase

with autowired attributes.

What's the best way to run the test in a different (integration test) environment? Subclass the test and override the ContextConfiguration?

@ContextConfiguration(locations = { "/my_spring_integration_test.xml" })
public class FooIntegrationTest extends FooTest

Would this work (I cannot currently easily test it here)? The problem with this approach is that "@ContextConfiguration(locations = { "/my_sp开发者_如何学Cring_integration_test.xml" })" is duplicated a lot.

Any suggestions?

Regards, Florian


I extended the GenericXmlContextLoader

public class MyContextLoader extends GenericXmlContextLoader {

and overrote the

protected String[] generateDefaultLocations(Class<?> clazz)

method to collect the config file names of a directory which I can specify by a SystemProperty (-Dtest.config=).

I also modified the follwowing method to NOT modify any locations

@Override
protected String[] modifyLocations(Class<?> clazz, String... locations) {
    return locations;
}

I use this context loader like this

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(loader = MyContextLoader.class)
public class Test { .... }

Running the test with a SystemProperty indicating the source of the config files enables you now to use completely different configurations.

The usage of a SystemProperty is of course only one strategy to specify the configuration location. You can do whatever you want in generateDefaultLocations().


EDIT:

This solution enables you to use complete different application context configurations (e.g. for mock objects) and not only different properties. You do not need a build step to deploy everything to your "classpath" location. My concrete implementation also used the users name as default to look for a configuration directory (src/test/resources/{user}) if no system property is given (makes it easy to maintain specific test environments for all developers on the project).

The usage of the PropertyPlaceholder ist still possible and recommended.


EDIT:

Spring Version 3.1.0 will support XML profiles/Environment Abstraction which is similar to my solution and will enable the choice of configuration files for different environments/profiles.


I'd go with this version:

ContextConfiguration(locations = { "/my_spring_test.xml" })
public abstract class AbstractMyTestCase extends AbstractJUnit4SpringContextTests

and in my_spring_test.xml, I'd use the PropertyPlaceHolderConfigurer mechanism.

Example for JPA:

<context:property-placeholder
    system-properties-mode="OVERRIDE" 
    location="classpath:test.properties" />

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
    destroy-method="close">
    <property name="driverClassName" value="${test.database.driver}" />
    <property name="url" value="${test.database.server}" />
    <property name="username" value="${test.database.user}" />
    <property name="password" value="${test.database.password}" />
</bean>

<bean id="entityManagerFactory"
    class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="persistenceUnitName" value="test" />
    <property name="dataSource" ref="dataSource" />
    <property name="persistenceXmlLocation"
             value="classpath:META-INF/persistence.xml" />
    <property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
    <property name="showSql" value="false" />
    <property name="generateDdl" value="${test.database.update}" />
    <property name="database" value="${test.database.databasetype}" />
</bean>
    </property>
</bean>

Now all you need to do is have different versions of test.properties on the class path for in-memory and real integration tests (and of course the respective driver classes need to be present). You can even set system properties to overwrite the property values.


If you want to prepare this with maven, you will find that copying files with maven is not trivial. You will need a way to execute code, the standard choices being the maven-antrun-plugin and gmaven-maven-plugin.

Either way: have two configuration files, e.g. in src/main/config and add two plugin executions, one in phase generate-test-resources and one in phase pre-integration-test. Here's the GMaven version:

<plugin>
    <groupId>org.codehaus.gmaven</groupId>
    <artifactId>gmaven-plugin</artifactId>
    <version>1.3</version>
    <executions>
        <execution>
            <phase>pre-integration-test</phase>
            <goals>
                <goal>execute</goal>
            </goals>
            <configuration>
            <source>
            new File(
               pom.build.testOutputDirectory,
               "test.properties"
            ).text = new File(
                       pom.basedir,
                       "src/main/config/int-test.properties"
            ).text;
            </source>
            </configuration>
        </execution>
        <execution>
            <phase>generate-test-resources</phase>
            <goals>
                <goal>execute</goal>
            </goals>
            <configuration>
            <source>
            new File(
               pom.build.testOutputDirectory,
               "test.properties"
            ).text = new File(
                       pom.basedir,
                       "src/main/config/memory-test.properties"
            ).text;
            </source>
            </configuration>
        </execution>
    </executions>
</plugin>


I have had no success in using Spring 3.x context:property-placeholder tag. I have used the old fashion bean tag along with a properties file and was able to set up a connection between my code and my database like so:

<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="location" value="/com/my/package/database.properties"/>
</bean>


<bean id="myDatasource" class="oracle.ucp.jdbc.PoolDataSourceFactory" 
    factory-method="getPoolDataSource">
    <property name="URL" value="${JDBC_URL}"/>
    <property name="user" value="${JDBC_USERNAME}"/>
    <property name="password" value="${JDBC_PASSWORD}"/>
    <property name="connectionFactoryClassName"   
      value="oracle.jdbc.pool.OracleConnectionPoolDataSource"/>
    <property name="ConnectionPoolName" value="SCDB_POOL"/>
    <property name="MinPoolSize" value="5"/>
    <property name="MaxPoolSize" value="50"/>
    <property name="connectionWaitTimeout" value="30"/>
    <property name="maxStatements" value="100"/>
</bean>

Here's an example of the properties file:

JDBC_URL=jdbc:oracle:thin:@myDB:1521:mySchema
JDBC_USERNAME=username
JDBC_PASSWORD=password

Then I set up my JUnit test like so:

@ContextConfiguration(locations = {"/com/my/pkg/test-system-context.xml"})
@RunWith(SpringJUnit4ClassRunner.class)
public class HeaderDaoTest {

    @Autowired
    HeaderDao headerDao;

    @Test
    public void validateHeaderId() {
        int headerId = 0;

        headerId = headerDao.getHeaderId();

        assertNotSame(0,headerId);
    }

}

That worked for me, but everybody does things a little differently. Hope this helps.


I recently ran in to the same problem and looking at the documentation for the @ContextConfiguration annotation, I noticed the inheritLocations option.

By adding this to my class e.g.

@ContextConfiguration(locations = { "/my_spring_integration_test.xml" }, inheritLocations=false)
public class FooIntegrationTest extends FooTest

I found that I was able to override the ContextConfiguration as desired.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜