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.
精彩评论