SQL queries or any sort of persistence components are very common in enterprise applications. More generally, persistence is a critical part in applications and that's why it should be thoroughly tested, whether it uses plain SQL or object/relational mapping tools like Hibernate. This article covers how to leverage the Spring TestContext Framework and DbUnit to test the persistence layer of an application. This first part shows a straightforward yet functional integration of both frameworks. A second part will then show how to integrate DbUnit into the Spring test framework in a more flexible way.
The code to be tested
The code to be tested is a simple repository (a.k.a DAO) which is based on Spring's SimpleJdbcTemplate
:
-
@Transactional
-
public
class JdbcContactRepository
implements ContactRepository
{
-
-
private SimpleJdbcTemplate jdbcTemplate;
-
-
public JdbcContactRepository
(DataSource dataSource
)
{
-
this.
jdbcTemplate =
new SimpleJdbcTemplate
(dataSource
);
-
}
-
-
@Override
-
public Contact create
(Contact contact
)
{
-
long nextId = jdbcTemplate.
queryForLong
(
"select nextval('contact_seq')"
);
-
jdbcTemplate.
update
(
-
"insert into contact (id,firstname,lastname) values (?,?,?)",
-
nextId,contact.
getFirstname
(
),contact.
getLastname
(
)
-
);
-
contact.
setId
(nextId
);
-
return contact;
-
}
-
-
@Override
-
public List
<Contact
> selectByLastname
(
String lastname
)
{
-
return jdbcTemplate.
query
(
-
"select * from contact where lastname like ?",
-
new ContactRowMapper
(
),
-
"%"+lastname+
"%"
-
);
-
}
-
-
private
static
class ContactRowMapper
implements RowMapper
<Contact
>
{
-
-
@Override
-
public Contact mapRow
(
ResultSet rs,
int rowNum
)
throws
SQLException
{
-
return
new Contact
(rs.
getLong
(
"id"
), rs.
getString
(
"firstname"
),rs.
getString
(
"lastname"
)
);
-
}
-
-
}
-
-
}
Nothing fancy so far. Just note that this repository class acts as our guinea pig for the persistence test demonstration. What we are about to see is valid for any persistence technology (plain JDBC, but also Hibernate, JPA, iBatis, etc.)
The application configuration
Here is a simple Spring configuration for declaring the repository and the transaction policy:
<beans (...)> <bean id="contactRepository" class="com.zenika.contact.repository.jdbc.JdbcContactRepository"> <constructor-arg ref="dataSource" /> </bean> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <tx:annotation-driven /> </beans>
The previous snippet shows what we usually call the "application configuration", as this configuration is all about application components and can be re-used across environments (production, testing). Note that this configuration is not self-sufficient, as it relies on a dataSource
bean, which is typically defined in a "infrastructure configuration" file. In production, the dataSource
bean can be defined as a JNDI lookup and for tests, it can be an embedded database.
Last note: the repositories and the transaction manager should also be defined in their own configuration files (that's how it's done in the code samples), but we gathered them here for brevity.
The test configuration
The test configuration re-uses the application configuration and defines a in-memory DataSource
and aSimpleJdbcTemplate
(useful for assertions in the test):
<beans (...)> <import resource="classpath:/contact-configuration.xml" /> <jdbc:embedded-database id="dataSource" type="H2"> <jdbc:script location="classpath:/contact-create.sql"/> </jdbc:embedded-database> <bean class="org.springframework.jdbc.core.simple.SimpleJdbcTemplate"> <constructor-arg ref="dataSource" /> </bean> </beans>
We use the Spring 3.0's jdbc
namespace, which creates a in-memory database and can also execute scripts on it after its creation. The contact-create.sql
file creates the table of the application, here is its content:
-
DROP sequence contact_seq
IF
EXISTS;
-
CREATE sequence contact_seq;
-
CREATE
TABLE contact
(
-
id int
,
-
firstname varchar
(
255
)
,
-
lastname varchar
(
255
)
,
-
constraint contact_pk
PRIMARY
KEY
(id
)
-
);
Let's see now how to write the test with the Spring TestContext Framework.
Writing a test with the Spring TestContext Framework
The Spring TestContext Framework allows to bootstrap a Spring application context for a test. It's thus useful forintegration tests, when you want to assert that several of your components integrate correctly with each other and/or with an external system like a database. That's exactly what we want for testing our repository: to check that the queries work properly when run on a database.
Using the Spring TestContext Framework is easy as it consists mainly in annotating a JUnit 4 test. Here is how to leverage the test framework:
-
package com.
zenika.
contact.
repository.
jdbc;
-
-
import org.junit.Test;
-
import org.junit.runner.RunWith;
-
import org.springframework.beans.factory.annotation.Autowired;
-
import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
-
import org.springframework.test.context.ContextConfiguration;
-
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
-
import com.zenika.contact.repository.ContactRepository;
-
-
@RunWith
(SpringJUnit4ClassRunner.
class
)
-
@ContextConfiguration
(locations=
{
-
"classpath:/repository-test-context.xml"
-
}
)
-
public
class JdbcContactRepositoryTest
{
-
-
@Autowired
-
ContactRepository contactRepository;
-
-
@Autowired
-
SimpleJdbcTemplate jdbcTemplate;
-
-
@Test
public
void create
(
)
{
-
// test method
-
}
-
-
}
Some explanations about how the test works:
- the
RunWith
annotation is from JUnit and it tells the test launcher (Eclipse, Maven, etc) which runner to use to execute the test. We hand off the execution to a Spring runner, which is the entry point of the Spring TestContext Framework. - with the
ContextConfiguration
annotation, we tell the runner to boostrap a Spring application context from the file(s) in thelocations
attribute of the annotation. - as the Spring test runner is in charge of the test lifecycle, it can inject some dependencies from the application context into it. We use this feature to inject the to-be-tested repository and the
SimpleJdbcTemplate
declared in the test infrastructure file.
The dependency injection feature is not the only thing the test framework brings. It also caches Spring application contexts in case they are used across tests. This is especially useful when you run a test suite that uses the same application context (e.g. the whole repository test suite): the application context is instantiated only once, which speeds up the execution of the tests (some beans like a Hibernate SessionFactory
or a JPA EntityManagerFactory
take some time to bootstrap as they have to compute all the mapping metadata).
The Spring TestContext Framework comes also with several annotations - to manage transactions across the test methods for instance - but we won't cover all of them in this article. Let's focus on the test and see how to test thecreate
method of the repository.
Testing the create method
What we want to check is the insert
query. For brevity's sake, we'll do this in a quick'n'dirty way, meaning we'll only check a new row has been inserted in the database (we won't check that this new row contains the exact same values we sent). Here is the content of the test of the create
method:
-
@RunWith
(SpringJUnit4ClassRunner.
class
)
-
@ContextConfiguration
(locations=
{
-
"classpath:/repository-test-context.xml"
-
}
)
-
public
class JdbcContactRepositoryTest
{
-
-
@Autowired
-
ContactRepository contactRepository;
-
-
@Autowired
-
SimpleJdbcTemplate jdbcTemplate;
-
-
@Autowired
-
DataSource dataSource;
-
-
@Test
public
void create
(
)
{
-
int initialCount = jdbcTemplate.
queryForInt
(
"select count(1) from contact"
);
-
Contact contact =
new Contact
(
"Arnaud",
"Cogoluegnes"
);
-
contactRepository.
create
(contact
);
-
Assert.
assertEquals
(initialCount+
1,jdbcTemplate.
queryForInt
(
"select count(1) from contact"
)
);
-
}
-
-
}
Note the use of the SimpleJdbcTemplate
as a test support class - its straightforward API is particularly suited for these cases (as well as for more advanced uses in applications). Now you can run the test with your favorite test launcher.
That's it for the create
method, let's see now how to test the selectByLastname
method.
Testing the selectByLastname method
Testing this method is more interesting as common testing problems show up. If we want to test this method correctly, the database needs to be in a known state. But if we don't do anything, we have no guaranty about such a known state, as any test than ran previously can have altered the database. So we are on our own: we need to put the database in a known state before testing the selectByLastname
. This can be done manually, thanks to an helper class like the SimpleJdbcTemplate
, but this solution doesn't scale when test data sets become more complex.
So we are going to use DbUnit, a framework dedicated to database tests. DbUnit allows to define data set into files (usually XML), comes with a API to inject these data sets into databases in different ways. DbUnit provides also helper classes to check the content of the database (mainly for assertion) and some base test classes.
Let's start immediatly with DbUnit by writing the data set (we're using DbUnit's "flat XML" format):
<dataset> <contact id="-1" firstname="Carl" lastname="Azoury" /> <contact id="-2" firstname="Olivier" lastname="Croisier" /> </dataset>
In the flat XML data set format, the tag and attribute names are mapped with tables and columns, respectively. This makes writing the data sets really simple. Note that DbUnit can create data sets from an existing database and that data sets are not meant to contain the whole content of the database (just the data needed for the test case).
Once the dataset is created, we need to inject it in the database before each method of the test. This is a job for a JUnit setUp
method. In JUnit 4, these methods need to be annotated with Before
:
-
@RunWith
(SpringJUnit4ClassRunner.
class
)
-
@ContextConfiguration
(locations=
{
-
"classpath:/repository-test-context.xml"
-
}
)
-
public
class JdbcContactRepositoryTest
{
-
-
@Autowired
-
ContactRepository contactRepository;
-
-
@Autowired
-
SimpleJdbcTemplate jdbcTemplate;
-
-
@Autowired
-
DataSource dataSource;
-
-
@Before
public
void setUp
(
)
throws
Exception
{
-
IDataSet dataSet =
new FlatXmlDataSetBuilder
(
).
build
(
new
File
(
-
"./src/test/resources/dataset.xml"
-
)
);
-
IDatabaseConnection dbConn =
new DatabaseDataSourceConnection
(dataSource
);
-
DatabaseOperation.
CLEAN_INSERT.
execute
(dbConn, dataSet
);
-
}
-
(...
)
-
}
We use DbUnit "clean insert" operation: it means that every table referenced in the data set is cleant before data are injected. Thanks to this, the create
test method doesn't interfere with the selectByLastname
method. The following snippet shows the whole test, with the selectByLastname
test method:
-
@RunWith
(SpringJUnit4ClassRunner.
class
)
-
@ContextConfiguration
(locations=
{
-
"classpath:/repository-test-context.xml"
-
}
)
-
public
class JdbcContactRepositoryTest
{
-
-
@Autowired
-
ContactRepository contactRepository;
-
-
@Autowired
-
SimpleJdbcTemplate jdbcTemplate;
-
-
@Autowired
-
DataSource dataSource;
-
-
@Before
public
void setUp
(
)
throws
Exception
{
-
IDataSet dataSet =
new FlatXmlDataSetBuilder
(
).
build
(
new
File
(
-
"./src/test/resources/dataset.xml"
-
)
);
-
IDatabaseConnection dbConn =
new DatabaseDataSourceConnection
(dataSource
);
-
DatabaseOperation.
CLEAN_INSERT.
execute
(dbConn, dataSet
);
-
}
-
-
@Test
public
void create
(
)
{
-
int initialCount = jdbcTemplate.
queryForInt
(
"select count(1) from contact"
);
-
Contact contact =
new Contact
(
"Arnaud",
"Cogoluegnes"
);
-
contactRepository.
create
(contact
);
-
Assert.
assertEquals
(initialCount+
1,jdbcTemplate.
queryForInt
(
"select count(1) from contact"
)
);
-
}
-
-
@Test
public
void selectByLastname
(
)
{
-
Assert.
assertEquals
(0,contactRepository.
selectByLastname
(
"cogo"
).
size
(
)
);
-
Assert.
assertEquals
(
1,contactRepository.
selectByLastname
(
"our"
).
size
(
)
);
-
Assert.
assertEquals
(
2,contactRepository.
selectByLastname
(
"o"
).
size
(
)
);
-
}
-
-
}
Summary
We saw how to leverage the Spring TestContext Framework and DbUnit for testing a repository. DbUnit is a very useful toolkit for database testing and we integrate some of its features into our test. This works quite nice: each method of our test is isolated from the others and can run its tests effectively as the database is in a known state. But our DbUnit integration is too simple: it needs some specific code in each test and this code will be scattered all over our test suite, each time we need to inject data in the database. That's why we'll see in the second part of this article how to integrate DbUnit in a more effective and flexible way, thanks to an extension of the Spring TestContext Framework. Thanks to this integration, tests won't depend on the DbUnit API any more and will be able to add the data set injection feature thanks to a couple of annotations.
The first part of this article covered the basics of the Spring TestContext Framework and DbUnit. We saw how to integrate DbUnit into a test to put the database into a known state before test methods are executed, to keep them isolated from each other. It worked great but this integration is too intrusive and does not scale when the number of test grows. This second part shows how to make the injection of test data into the database a cross-cutting concern, to let tests focus on their job and to allow them adding this feature with an optional, annotation-based configuration.
The usual suspect
Remember, the test of our repository looked like this:
-
@RunWith
(SpringJUnit4ClassRunner.
class
)
-
@ContextConfiguration
(locations=
{
-
"classpath:/repository-test-context.xml"
-
}
)
-
public
class JdbcContactRepositoryTest
{
-
-
@Autowired
-
ContactRepository contactRepository;
-
-
@Autowired
-
SimpleJdbcTemplate jdbcTemplate;
-
-
@Autowired
-
DataSource dataSource;
-
-
@Before
public
void setUp
(
)
throws
Exception
{
-
IDataSet dataSet =
new FlatXmlDataSetBuilder
(
).
build
(
new
File
(
-
"./src/test/resources/dataset.xml"
-
)
);
-
IDatabaseConnection dbConn =
new DatabaseDataSourceConnection
(dataSource
);
-
DatabaseOperation.
CLEAN_INSERT.
execute
(dbConn, dataSet
);
-
}
-
-
@Test
public
void create
(
)
{
-
int initialCount = jdbcTemplate.
queryForInt
(
"select count(1) from contact"
);
-
Contact contact =
new Contact
(
"Arnaud",
"Cogoluegnes"
);
-
contactRepository.
create
(contact
);
-
Assert.
assertEquals
(initialCount+
1,jdbcTemplate.
queryForInt
(
"select count(1) from contact"
)
);
-
}
-
-
@Test
public
void selectByLastname
(
)
{
-
Assert.
assertEquals
(0,contactRepository.
selectByLastname
(
"cogo"
).
size
(
)
);
-
Assert.
assertEquals
(
1,contactRepository.
selectByLastname
(
"our"
).
size
(
)
);
-
Assert.
assertEquals
(
2,contactRepository.
selectByLastname
(
"o"
).
size
(
)
);
-
}
-
-
}
The test injects itself the test data set in the setUp
method, by leveraging the DbUnit API. It works fine, but wouldn't it be better if the test could only say "I want this data set to be injected before each of my test methods" and let someone else do the job? This approach makes sense as JUnit tests are a good example of the Inversion of Control pattern: an external component - the runner - is in charge of calling the test methods. This makes it possible to add behavior around the test methods, namely during the lifecycle of the test.
Let's see how the Spring TestContext Framework allows us to do that.
TestExecutionListeners to react to the test lifecycle
The Spring TestContext Framework provides a very simple yet powerful extension hook: the TestExecutionListener
. The Spring test runner maintains a list of TestExecutionListener
s that can react to the test lifecycle. Here is the definition of the TestExecutionListener
interface:
-
public
interface TestExecutionListener
{
-
-
void beforeTestClass
(TestContext testContext
)
throws
Exception;
-
-
void prepareTestInstance
(TestContext testContext
)
throws
Exception;
-
-
void beforeTestMethod
(TestContext testContext
)
throws
Exception;
-
-
void afterTestMethod
(TestContext testContext
)
throws
Exception;
-
-
void afterTestClass
(TestContext testContext
)
throws
Exception;
-
-
}
Spring provides some TestExecutionListener
s: one of them, the DependencyInjectionTestExecutionListener
is in charge of injecting dependencies from the Spring application context into the test instance. This is this listener that interprets the Autowired
annotation we add on the test properties. This means that Spring adds automatically someTestExecutionListener
s on its default list.
Imagine we add the data set injection feature by implementing our TestExecutionListener
, theCleanInsertTestExecutionListener
. We can add our own listeners by using the TestExecutionListeners
annotation on the test class:
-
@RunWith
(SpringJUnit4ClassRunner.
class
)
-
@ContextConfiguration
(locations=
{
-
"classpath:/repository-test-context.xml"
-
}
)
-
@TestExecutionListeners
(
-
{DependencyInjectionTestExecutionListener.
class,CleanInsertTestExecutionListener.
class
}
-
)
-
public
class JdbcContactRepositoryTest
{
-
(...
)
-
}
Note that as soon as we use the TestExecutionListeners
annotation, we override the default stack of listeners, which means we have to add ourselves the ones we need (that's what we do with the dependency injection listener).
We have enough theory to build our CleanInsertTestExecutionListener
!
Writing the CleanInsertTestExecutionListener
So our CleanInsertTestExecutionListener
will inject data into the test database during the beforeTestMethod
. But how can he know about the data set to inject? A nice way to locate the data set would be to provide a default behavior ("convention over configuration") but also allows explicit configuration. Let's see first the explicit configuration! What about a annotation? The test class could accept a annotation that locates the data set. Let's call this annotationDataSetLocation
. Here is how to use it on the test:
-
@RunWith
(SpringJUnit4ClassRunner.
class
)
-
@ContextConfiguration
(locations=
{
-
"classpath:/repository-test-context.xml"
-
}
)
-
@TestExecutionListeners
(
-
{DependencyInjectionTestExecutionListener.
class,CleanInsertTestExecutionListener.
class
}
-
)
-
@DataSetLocation
(
"classpath:/dataset.xml"
)
-
public
class JdbcContactRepositoryTest
{
-
(...
)
-
}
Note the annotation accepts the syntax of the Spring's resource abstraction (prefixes like classpath
, file
, etc.). The definition of the annotation is straightforward:
-
package com.
zenika.
test;
-
-
import java.lang.annotation.Documented;
-
import java.lang.annotation.ElementType;
-
import java.lang.annotation.Inherited;
-
import java.lang.annotation.Retention;
-
import java.lang.annotation.RetentionPolicy;
-
import java.lang.annotation.Target;
-
-
@Retention
(RetentionPolicy.
RUNTIME
)
-
@Target
(ElementType.
TYPE
)
-
@Inherited
-
@Documented
-
public @
interface DataSetLocation
{
-
-
public
String value
(
);
-
-
}
And what about the default behavior? Let's say the default location of a data set would be based on the class name of the test, with -dataset.xml
added at the end. The file must be on the class path, in the same package as the test.
We have now everything to write the CleanInsertTestExecutionListener
:
-
package com.
zenika.
test;
-
-
import javax.sql.DataSource;
-
import org.dbunit.database.DatabaseDataSourceConnection;
-
import org.dbunit.database.IDatabaseConnection;
-
import org.dbunit.dataset.IDataSet;
-
import org.dbunit.dataset.xml.FlatXmlDataSetBuilder;
-
import org.dbunit.operation.DatabaseOperation;
-
import org.slf4j.Logger;
-
import org.slf4j.LoggerFactory;
-
import org.springframework.core.io.Resource;
-
import org.springframework.test.context.TestContext;
-
import org.springframework.test.context.TestExecutionListener;
-
import org.springframework.util.StringUtils;
-
-
public
class CleanInsertTestExecutionListener
implements TestExecutionListener
{
-
-
private
static
final Logger LOG = LoggerFactory.
getLogger
(CleanInsertTestExecutionListener.
class
);
-
-
@Override
-
public
void beforeTestMethod
(TestContext testContext
)
throws
Exception
{
-
// location of the data set
-
String dataSetResourcePath =
null;
-
-
// first, the annotation on the test class
-
DataSetLocation dsLocation = testContext.
getTestInstance
(
).
getClass
(
).
getAnnotation
(
-
DataSetLocation.
class
-
);
-
if
(dsLocation
!=
null
)
{
-
// found the annotation
-
dataSetResourcePath = dsLocation.
value
(
);
-
LOG.
info
(
"annotated test, using data set: {}",dataSetResourcePath
);
-
}
else
{
-
// no annotation, let's try with the name of the test
-
String tempDsRes = testContext.
getTestInstance
(
).
getClass
(
).
getName
(
);
-
tempDsRes = StringUtils.
replace
(tempDsRes,
".",
"/"
);
-
tempDsRes =
"/"+tempDsRes+
"-dataset.xml";
-
if
(getClass
(
).
getResourceAsStream
(tempDsRes
)
!=
null
)
{
-
LOG.
info
(
"detected default dataset: {}",tempDsRes
);
-
dataSetResourcePath = tempDsRes;
-
}
else
{
-
LOG.
info
(
"no default dataset"
);
-
}
-
}
-
-
if
(dataSetResourcePath
!=
null
)
{
-
Resource dataSetResource = testContext.
getApplicationContext
(
).
getResource
(
-
dataSetResourcePath
-
);
-
IDataSet dataSet =
new FlatXmlDataSetBuilder
(
).
build
(dataSetResource.
getInputStream
(
)
);
-
IDatabaseConnection dbConn =
new DatabaseDataSourceConnection
(
-
testContext.
getApplicationContext
(
).
getBean
(DataSource.
class
)
-
);
-
DatabaseOperation.
CLEAN_INSERT.
execute
(dbConn, dataSet
);
-
}
else
{
-
LOG.
info
(
"{} does not have any data set, no data injection",testContext.
getClass
(
).
getName
(
)
);
-
}
-
-
}
-
-
(...
) the remaining methods of the
interface are not used
-
-
}
The implementation of the test execution listener is rather simple. Let's see how to change the test now.
The new version of the database test
The test doesn't need the setUp
any more, only the TestExecutionListeners
annotation to customize the default list and the DataSetLocation
annotation to locate the data set:
-
@RunWith
(SpringJUnit4ClassRunner.
class
)
-
@ContextConfiguration
(locations=
{
-
"classpath:/repository-test-context.xml"
-
}
)
-
@TestExecutionListeners
(
-
{DependencyInjectionTestExecutionListener.
class,CleanInsertTestExecutionListener.
class
}
-
)
-
@DataSetLocation
(
"classpath:/dataset.xml"
)
-
public
class JdbcContactRepositoryTest
{
-
-
@Autowired
-
ContactRepository contactRepository;
-
-
@Autowired
-
SimpleJdbcTemplate jdbcTemplate;
-
-
@Test
public
void create
(
)
{
-
int initialCount = jdbcTemplate.
queryForInt
(
"select count(1) from contact"
);
-
Contact contact =
new Contact
(
"Arnaud",
"Cogoluegnes"
);
-
contactRepository.
create
(contact
);
-
Assert.
assertEquals
(initialCount+
1,jdbcTemplate.
queryForInt
(
"select count(1) from contact"
)
);
-
}
-
-
@Test
public
void selectByLastname
(
)
{
-
Assert.
assertEquals
(0,contactRepository.
selectByLastname
(
"cogo"
).
size
(
)
);
-
Assert.
assertEquals
(
1,contactRepository.
selectByLastname
(
"our"
).
size
(
)
);
-
Assert.
assertEquals
(
2,contactRepository.
selectByLastname
(
"o"
).
size
(
)
);
-
}
-
-
}
Note we could have moved the data set file beside the test class, called it JdbcContactRepositoryTest-dataset.xml
and we could have removed the DataSetLocation
!
What have we done exactly? We made a cross-cutting concern of the data injection into the database. Any test can benefit from this feature just by adding some annotations, no need to extend any class or implement any interface. The data injection feature is now centralized into the CleanInsertTestExecutionListener
: if we add any feature to it, any test will be able to benefit from it. This is far more powerful that the simple approach we chose first, which would have scattered the code into all test classes. Let's enhance our execution listener thanks to a DbUnit's feature.
Enhancing the CleanInsertTestExecutionListener with substitution
Imagine that some data in the data set can't be static but should be computed at runtime. A good example would be a date column that must take the date of the day for the test. We would like to be able to use a kind of variable in our data sets, like the following:
<dataset> <contact id="-1" firstname="Carl" lastname="Azoury" creation_date="[NOW]" /> <contact id="-2" firstname="Olivier" lastname="Croisier" creation_date="[NOW]" /> </dataset>
We can do this thanks to DbUnit's ReplacementDataSet
, which acts as a decorator on a data set and accepts substition patterns. The following snippet shows how to modify the data set injection to make a [NOW]
variable available:
-
Resource dataSetResource = testContext.
getApplicationContext
(
).
getResource
(dataSetResourcePath
);
-
IDataSet dataSet =
new FlatXmlDataSetBuilder
(
).
build
(dataSetResource.
getInputStream
(
)
);
-
ReplacementDataSet replaceDataSet =
new ReplacementDataSet
(dataSet
);
-
replaceDataSet.
addReplacementObject
(
"[NULL]",
null
);
-
Calendar cal =
Calendar.
getInstance
(
Locale.
getDefault
(
)
);
-
replaceDataSet.
addReplacementObject
(
"[NOW]", cal.
getTime
(
)
);
-
IDatabaseConnection dbConn =
new DatabaseDataSourceConnection
(
-
testContext.
getApplicationContext
(
).
getBean
(DataSource.
class
)
-
);
-
DatabaseOperation.
CLEAN_INSERT.
execute
(dbConn, replaceDataSet
);
That's it! A new feature added to our CleanInsertTestExecutionListener
! This is only an example, we can imagine many more features: deleting the whole content of the database before injecting the data set, data sets injected on a per-method basis thanks to annotations on the method itself, etc.
Summary
We pushed our integration between the Spring TestContext Framework and DbUnit a step further in this second part. Injecting data in the database before executing test methods became a cross-cutting concern. This is a good example of how Inversion of Control and annotations allow to add behavoir around methods execution in a very simple way. It makes the data injection feature more flexible, easier to use and centralized.