8

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_spring_integration_test.xml" })" is duplicated a lot.

Any suggestions?

Regards, Florian

Sean Patrick Floyd
  • 292,901
  • 67
  • 465
  • 588
Puce
  • 37,247
  • 13
  • 80
  • 152

4 Answers4

4

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.

FrVaBe
  • 47,963
  • 16
  • 124
  • 157
2

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>
Sean Patrick Floyd
  • 292,901
  • 67
  • 465
  • 588
  • Yes, I'm already using property-placeholder and thought about replacing the file on the classpath. What's the easiest way to do this with Maven? Separate projects/ dependencies which just contain a single properties file and then tweak the dependencies somehow dependent on phase/ profile? – Puce Jan 04 '11 at 13:35
  • Thanks, though I don't want to introduce Groovy to the project at this point. I think the problem is that Maven is lacking proper integration test support: http://willcode4beer.com/opinion.jsp?set=maven2_integration-test I'll check if I can do it with the mave-antrun-pluging or even with the Maven Resources Plugin (resources:copy-resources) – Puce Jan 04 '11 at 14:32
  • You are not introducing groovy to the project, just adding two lines of groovy to the build. Your project would still have no dependencies to Groovy beyond the build (no jars need to be deployed). Same with antrun, your project would have no dependencies to ant. – Sean Patrick Floyd Jan 04 '11 at 14:34
  • Hmm, it seems both answers are viable but only one can be selected as the correct answer...? – Puce Jan 04 '11 at 14:36
  • @Puce choose or throw a coin :-) – Sean Patrick Floyd Jan 04 '11 at 14:37
  • Yes, I know, but still somebody has to maintain the POM and most likely it won't be me. I guess more people are familiar with Ant than with Groovy. – Puce Jan 04 '11 at 14:39
  • @Puce the nice thing about Groovy is that the source is 95% compatible with Java. Groovy = Java + additional methods on JDK classes + some syntactic sugar – Sean Patrick Floyd Jan 04 '11 at 14:42
0

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.

Levity
  • 315
  • 3
  • 11
0

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.

JavaDev03
  • 141
  • 2
  • 9