Testing SQL queries with Spring and DbUnit, part 1

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:


   
   
  1. @Transactional
  2. public class JdbcContactRepository implements ContactRepository {
  3.  
  4. private SimpleJdbcTemplate jdbcTemplate;
  5.  
  6. public JdbcContactRepository (DataSource dataSource ) {
  7. this. jdbcTemplate = new SimpleJdbcTemplate (dataSource );
  8. }
  9.  
  10. @Override
  11. public Contact create (Contact contact ) {
  12. long nextId = jdbcTemplate. queryForLong ( "select nextval('contact_seq')" );
  13. jdbcTemplate. update (
  14. "insert into contact (id,firstname,lastname) values (?,?,?)",
  15. nextId,contact. getFirstname ( ),contact. getLastname ( )
  16. );
  17. contact. setId (nextId );
  18. return contact;
  19. }
  20.  
  21. @Override
  22. public List <Contact > selectByLastname ( String lastname ) {
  23. return jdbcTemplate. query (
  24. "select * from contact where lastname like ?",
  25. new ContactRowMapper ( ),
  26. "%"+lastname+ "%"
  27. );
  28. }
  29.  
  30. private static class ContactRowMapper implements RowMapper <Contact > {
  31.  
  32. @Override
  33. public Contact mapRow ( ResultSet rs, int rowNum ) throws SQLException {
  34. return new Contact (rs. getLong ( "id" ), rs. getString ( "firstname" ),rs. getString ( "lastname" ) );
  35. }
  36.  
  37. }
  38.  
  39. }

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:

  
  
  1. <beans (...)>
  2.  
  3. <bean id="contactRepository" class="com.zenika.contact.repository.jdbc.JdbcContactRepository">
  4. <constructor-arg ref="dataSource" />
  5. </bean>
  6.  
  7. <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  8. <property name="dataSource" ref="dataSource" />
  9. </bean>
  10.  
  11. <tx:annotation-driven />
  12.  
  13. </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):

  
  
  1. <beans (...)>
  2.  
  3. <import resource="classpath:/contact-configuration.xml" />
  4.  
  5. <jdbc:embedded-database id="dataSource" type="H2">
  6. <jdbc:script location="classpath:/contact-create.sql"/>
  7. </jdbc:embedded-database>
  8.  
  9. <bean class="org.springframework.jdbc.core.simple.SimpleJdbcTemplate">
  10. <constructor-arg ref="dataSource" />
  11. </bean>
  12.  
  13. </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:


   
   
  1. DROP sequence contact_seq IF EXISTS;
  2. CREATE sequence contact_seq;
  3. CREATE TABLE contact (
  4. id int ,
  5. firstname varchar ( 255 ) ,
  6. lastname varchar ( 255 ) ,
  7. constraint contact_pk PRIMARY KEY (id )
  8. );

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:


   
   
  1. package com. zenika. contact. repository. jdbc;
  2.  
  3. import org.junit.Test;
  4. import org.junit.runner.RunWith;
  5. import org.springframework.beans.factory.annotation.Autowired;
  6. import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
  7. import org.springframework.test.context.ContextConfiguration;
  8. import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
  9. import com.zenika.contact.repository.ContactRepository;
  10.  
  11. @RunWith (SpringJUnit4ClassRunner. class )
  12. @ContextConfiguration (locations= {
  13. "classpath:/repository-test-context.xml"
  14. } )
  15. public class JdbcContactRepositoryTest {
  16.  
  17. @Autowired
  18. ContactRepository contactRepository;
  19.  
  20. @Autowired
  21. SimpleJdbcTemplate jdbcTemplate;
  22.  
  23. @Test public void create ( ) {
  24. // test method
  25. }
  26.  
  27. }

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 the locations 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:


   
   
  1. @RunWith (SpringJUnit4ClassRunner. class )
  2. @ContextConfiguration (locations= {
  3. "classpath:/repository-test-context.xml"
  4. } )
  5. public class JdbcContactRepositoryTest {
  6.  
  7. @Autowired
  8. ContactRepository contactRepository;
  9.  
  10. @Autowired
  11. SimpleJdbcTemplate jdbcTemplate;
  12.  
  13. @Autowired
  14. DataSource dataSource;
  15.  
  16. @Test public void create ( ) {
  17. int initialCount = jdbcTemplate. queryForInt ( "select count(1) from contact" );
  18. Contact contact = new Contact ( "Arnaud", "Cogoluegnes" );
  19. contactRepository. create (contact );
  20. Assert. assertEquals (initialCount+ 1,jdbcTemplate. queryForInt ( "select count(1) from contact" ) );
  21. }
  22.  
  23. }

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):

  
  
  1. <dataset>
  2.  
  3. <contact id="-1" firstname="Carl" lastname="Azoury" />
  4. <contact id="-2" firstname="Olivier" lastname="Croisier" />
  5.  
  6. </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:


   
   
  1. @RunWith (SpringJUnit4ClassRunner. class )
  2. @ContextConfiguration (locations= {
  3. "classpath:/repository-test-context.xml"
  4. } )
  5. public class JdbcContactRepositoryTest {
  6.  
  7. @Autowired
  8. ContactRepository contactRepository;
  9.  
  10. @Autowired
  11. SimpleJdbcTemplate jdbcTemplate;
  12.  
  13. @Autowired
  14. DataSource dataSource;
  15.  
  16. @Before public void setUp ( ) throws Exception {
  17. IDataSet dataSet = new FlatXmlDataSetBuilder ( ). build ( new File (
  18. "./src/test/resources/dataset.xml"
  19. ) );
  20. IDatabaseConnection dbConn = new DatabaseDataSourceConnection (dataSource );
  21. DatabaseOperation. CLEAN_INSERT. execute (dbConn, dataSet );
  22. }
  23. (... )
  24. }

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:


   
   
  1. @RunWith (SpringJUnit4ClassRunner. class )
  2. @ContextConfiguration (locations= {
  3. "classpath:/repository-test-context.xml"
  4. } )
  5. public class JdbcContactRepositoryTest {
  6.  
  7. @Autowired
  8. ContactRepository contactRepository;
  9.  
  10. @Autowired
  11. SimpleJdbcTemplate jdbcTemplate;
  12.  
  13. @Autowired
  14. DataSource dataSource;
  15.  
  16. @Before public void setUp ( ) throws Exception {
  17. IDataSet dataSet = new FlatXmlDataSetBuilder ( ). build ( new File (
  18. "./src/test/resources/dataset.xml"
  19. ) );
  20. IDatabaseConnection dbConn = new DatabaseDataSourceConnection (dataSource );
  21. DatabaseOperation. CLEAN_INSERT. execute (dbConn, dataSet );
  22. }
  23.  
  24. @Test public void create ( ) {
  25. int initialCount = jdbcTemplate. queryForInt ( "select count(1) from contact" );
  26. Contact contact = new Contact ( "Arnaud", "Cogoluegnes" );
  27. contactRepository. create (contact );
  28. Assert. assertEquals (initialCount+ 1,jdbcTemplate. queryForInt ( "select count(1) from contact" ) );
  29. }
  30.  
  31. @Test public void selectByLastname ( ) {
  32. Assert. assertEquals (0,contactRepository. selectByLastname ( "cogo" ). size ( ) );
  33. Assert. assertEquals ( 1,contactRepository. selectByLastname ( "our" ). size ( ) );
  34. Assert. assertEquals ( 2,contactRepository. selectByLastname ( "o" ). size ( ) );
  35. }
  36.  
  37. }

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:


    
    
  1. @RunWith (SpringJUnit4ClassRunner. class )
  2. @ContextConfiguration (locations= {
  3. "classpath:/repository-test-context.xml"
  4. } )
  5. public class JdbcContactRepositoryTest {
  6.  
  7. @Autowired
  8. ContactRepository contactRepository;
  9.  
  10. @Autowired
  11. SimpleJdbcTemplate jdbcTemplate;
  12.  
  13. @Autowired
  14. DataSource dataSource;
  15.  
  16. @Before public void setUp ( ) throws Exception {
  17. IDataSet dataSet = new FlatXmlDataSetBuilder ( ). build ( new File (
  18. "./src/test/resources/dataset.xml"
  19. ) );
  20. IDatabaseConnection dbConn = new DatabaseDataSourceConnection (dataSource );
  21. DatabaseOperation. CLEAN_INSERT. execute (dbConn, dataSet );
  22. }
  23.  
  24. @Test public void create ( ) {
  25. int initialCount = jdbcTemplate. queryForInt ( "select count(1) from contact" );
  26. Contact contact = new Contact ( "Arnaud", "Cogoluegnes" );
  27. contactRepository. create (contact );
  28. Assert. assertEquals (initialCount+ 1,jdbcTemplate. queryForInt ( "select count(1) from contact" ) );
  29. }
  30.  
  31. @Test public void selectByLastname ( ) {
  32. Assert. assertEquals (0,contactRepository. selectByLastname ( "cogo" ). size ( ) );
  33. Assert. assertEquals ( 1,contactRepository. selectByLastname ( "our" ). size ( ) );
  34. Assert. assertEquals ( 2,contactRepository. selectByLastname ( "o" ). size ( ) );
  35. }
  36.  
  37. }

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 TestExecutionListeners that can react to the test lifecycle. Here is the definition of the TestExecutionListener interface:


    
    
  1. public interface TestExecutionListener {
  2.  
  3. void beforeTestClass (TestContext testContext ) throws Exception;
  4.  
  5. void prepareTestInstance (TestContext testContext ) throws Exception;
  6.  
  7. void beforeTestMethod (TestContext testContext ) throws Exception;
  8.  
  9. void afterTestMethod (TestContext testContext ) throws Exception;
  10.  
  11. void afterTestClass (TestContext testContext ) throws Exception;
  12.  
  13. }

Spring provides some TestExecutionListeners: 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 someTestExecutionListeners 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:


    
    
  1. @RunWith (SpringJUnit4ClassRunner. class )
  2. @ContextConfiguration (locations= {
  3. "classpath:/repository-test-context.xml"
  4. } )
  5. @TestExecutionListeners (
  6. {DependencyInjectionTestExecutionListener. class,CleanInsertTestExecutionListener. class }
  7. )
  8. public class JdbcContactRepositoryTest {
  9. (... )
  10. }

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:


    
    
  1. @RunWith (SpringJUnit4ClassRunner. class )
  2. @ContextConfiguration (locations= {
  3. "classpath:/repository-test-context.xml"
  4. } )
  5. @TestExecutionListeners (
  6. {DependencyInjectionTestExecutionListener. class,CleanInsertTestExecutionListener. class }
  7. )
  8. @DataSetLocation ( "classpath:/dataset.xml" )
  9. public class JdbcContactRepositoryTest {
  10. (... )
  11. }

Note the annotation accepts the syntax of the Spring's resource abstraction (prefixes like classpathfile, etc.). The definition of the annotation is straightforward:


    
    
  1. package com. zenika. test;
  2.  
  3. import java.lang.annotation.Documented;
  4. import java.lang.annotation.ElementType;
  5. import java.lang.annotation.Inherited;
  6. import java.lang.annotation.Retention;
  7. import java.lang.annotation.RetentionPolicy;
  8. import java.lang.annotation.Target;
  9.  
  10. @Retention (RetentionPolicy. RUNTIME )
  11. @Target (ElementType. TYPE )
  12. @Inherited
  13. @Documented
  14. public @ interface DataSetLocation {
  15.  
  16. public String value ( );
  17.  
  18. }

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:


    
    
  1. package com. zenika. test;
  2.  
  3. import javax.sql.DataSource;
  4. import org.dbunit.database.DatabaseDataSourceConnection;
  5. import org.dbunit.database.IDatabaseConnection;
  6. import org.dbunit.dataset.IDataSet;
  7. import org.dbunit.dataset.xml.FlatXmlDataSetBuilder;
  8. import org.dbunit.operation.DatabaseOperation;
  9. import org.slf4j.Logger;
  10. import org.slf4j.LoggerFactory;
  11. import org.springframework.core.io.Resource;
  12. import org.springframework.test.context.TestContext;
  13. import org.springframework.test.context.TestExecutionListener;
  14. import org.springframework.util.StringUtils;
  15.  
  16. public class CleanInsertTestExecutionListener implements TestExecutionListener {
  17.  
  18. private static final Logger LOG = LoggerFactory. getLogger (CleanInsertTestExecutionListener. class );
  19.  
  20. @Override
  21. public void beforeTestMethod (TestContext testContext ) throws Exception {
  22. // location of the data set
  23. String dataSetResourcePath = null;
  24.  
  25. // first, the annotation on the test class
  26. DataSetLocation dsLocation = testContext. getTestInstance ( ). getClass ( ). getAnnotation (
  27. DataSetLocation. class
  28. );
  29. if (dsLocation != null ) {
  30. // found the annotation
  31. dataSetResourcePath = dsLocation. value ( );
  32. LOG. info ( "annotated test, using data set: {}",dataSetResourcePath );
  33. } else {
  34. // no annotation, let's try with the name of the test
  35. String tempDsRes = testContext. getTestInstance ( ). getClass ( ). getName ( );
  36. tempDsRes = StringUtils. replace (tempDsRes, ".", "/" );
  37. tempDsRes = "/"+tempDsRes+ "-dataset.xml";
  38. if (getClass ( ). getResourceAsStream (tempDsRes ) != null ) {
  39. LOG. info ( "detected default dataset: {}",tempDsRes );
  40. dataSetResourcePath = tempDsRes;
  41. } else {
  42. LOG. info ( "no default dataset" );
  43. }
  44. }
  45.  
  46. if (dataSetResourcePath != null ) {
  47. Resource dataSetResource = testContext. getApplicationContext ( ). getResource (
  48. dataSetResourcePath
  49. );
  50. IDataSet dataSet = new FlatXmlDataSetBuilder ( ). build (dataSetResource. getInputStream ( ) );
  51. IDatabaseConnection dbConn = new DatabaseDataSourceConnection (
  52. testContext. getApplicationContext ( ). getBean (DataSource. class )
  53. );
  54. DatabaseOperation. CLEAN_INSERT. execute (dbConn, dataSet );
  55. } else {
  56. LOG. info ( "{} does not have any data set, no data injection",testContext. getClass ( ). getName ( ) );
  57. }
  58.  
  59. }
  60.  
  61. (... ) the remaining methods of the interface are not used
  62.  
  63. }

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:


    
    
  1. @RunWith (SpringJUnit4ClassRunner. class )
  2. @ContextConfiguration (locations= {
  3. "classpath:/repository-test-context.xml"
  4. } )
  5. @TestExecutionListeners (
  6. {DependencyInjectionTestExecutionListener. class,CleanInsertTestExecutionListener. class }
  7. )
  8. @DataSetLocation ( "classpath:/dataset.xml" )
  9. public class JdbcContactRepositoryTest {
  10.  
  11. @Autowired
  12. ContactRepository contactRepository;
  13.  
  14. @Autowired
  15. SimpleJdbcTemplate jdbcTemplate;
  16.  
  17. @Test public void create ( ) {
  18. int initialCount = jdbcTemplate. queryForInt ( "select count(1) from contact" );
  19. Contact contact = new Contact ( "Arnaud", "Cogoluegnes" );
  20. contactRepository. create (contact );
  21. Assert. assertEquals (initialCount+ 1,jdbcTemplate. queryForInt ( "select count(1) from contact" ) );
  22. }
  23.  
  24. @Test public void selectByLastname ( ) {
  25. Assert. assertEquals (0,contactRepository. selectByLastname ( "cogo" ). size ( ) );
  26. Assert. assertEquals ( 1,contactRepository. selectByLastname ( "our" ). size ( ) );
  27. Assert. assertEquals ( 2,contactRepository. selectByLastname ( "o" ). size ( ) );
  28. }
  29.  
  30. }

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:

   
   
  1. <dataset>
  2.  
  3. <contact id="-1" firstname="Carl" lastname="Azoury" creation_date="[NOW]" />
  4. <contact id="-2" firstname="Olivier" lastname="Croisier" creation_date="[NOW]" />
  5.  
  6. </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:


    
    
  1. Resource dataSetResource = testContext. getApplicationContext ( ). getResource (dataSetResourcePath );
  2. IDataSet dataSet = new FlatXmlDataSetBuilder ( ). build (dataSetResource. getInputStream ( ) );
  3. ReplacementDataSet replaceDataSet = new ReplacementDataSet (dataSet );
  4. replaceDataSet. addReplacementObject ( "[NULL]", null );
  5. Calendar cal = Calendar. getInstance ( Locale. getDefault ( ) );
  6. replaceDataSet. addReplacementObject ( "[NOW]", cal. getTime ( ) );
  7. IDatabaseConnection dbConn = new DatabaseDataSourceConnection (
  8. testContext. getApplicationContext ( ). getBean (DataSource. class )
  9. );
  10. 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.


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值