Overriding DbUnit Configuration
After we have finished this lesson, we:
- Know how we can create a custom data set loader that allows us to use so called replacement data sets.
- Understand how we can change the default DbUnit configuration.
This lesson assumes that:
Introduction to the @DbUnitConfiguration Annotation
The @DbUnitConfiguration annotation allows us to customize the configuration of DbUnitTestExecutionListener. This annotation has four attributes that are described in the following:
- The
databaseConnectionattribute contains the names of the Spring beans which provide database connections for DbUnit. - The
dataSetLoaderattribute contains the data set loader class that is used to load our DbUnit data sets. - The
dataSetLoaderBeanattribute contains the name of the data set loader bean that is used to load our DbUnit data sets. - The
databaseOperationLookupattribute contains the class that is used to lookup DbUnit database operations.
Let’s move on and find out how we can create a custom data set loader, which allows us to use so called replacement data sets, and use this data set loader for loading our DbUnit data sets.
Using Replacement Data Sets
The first thing that we have to do is to create a custom data set loader class. We can create our data set loader class by following these steps:
First, we have to create a class that extends the FlatXmlDataSetLoader class. We have to extend that class because we want that our data set loader supports flat XML data sets and enables column sensing.
After we have created our data set loader class, its source code looks as follows:
1import com.github.springtestdbunit.dataset.FlatXmlDataSetLoader;
2
3public class ReplacementDataSetLoader extends FlatXmlDataSetLoader {
4
5}
Second, we have to create a private method that transforms an IDataSet object into a ReplacementDataSet object and returns the created object. This method configures the placeholder strings and the actual values that are used to replace these placeholder strings.
We can implement this method by following these steps:
- Add a private
createReplacementDataSet()method into our data set loader class. This method takes anIDataSetobject as a method parameter and returns anIDataSetobject. - Create a new
ReplacementDataSetobject. - Ensure that the
[empty_string]strings found from our data set are replaced with an empty string. - Ensure that the
[null]strings found from our data set are replaced withnullvalues. - Return the created
ReplacementDataSetobject.
After we have implemented this method, the source code of the ReplacementDataSetLoader class looks as follows:
1import com.github.springtestdbunit.dataset.FlatXmlDataSetLoader;
2import org.dbunit.dataset.IDataSet;
3import org.dbunit.dataset.ReplacementDataSet;
4
5public class ReplacementDataSetLoader extends FlatXmlDataSetLoader {
6
7 private IDataSet createReplacementDataSet(IDataSet flatXmlDataSet) {
8 ReplacementDataSet replacementDataSet =
9 new ReplacementDataSet(flatXmlDataSet);
10
11 replacementDataSet.addReplacementObject("[empty_string]", "");
12 replacementDataSet.addReplacementObject("[null]", null);
13
14 return replacementDataSet;
15 }
16}
Third, we have override the createDataSet() method. When we implement this method, we have to obtain the loaded flat XML data set by invoking the createDataSet() method of the super class and transform this data set into a replacement data set.
After we have implemented this method, the source code of our data set loader class looks as follows:
1import com.github.springtestdbunit.dataset.FlatXmlDataSetLoader;
2import org.dbunit.dataset.IDataSet;
3import org.dbunit.dataset.ReplacementDataSet;
4import org.springframework.core.io.Resource;
5
6public class ReplacementDataSetLoader extends FlatXmlDataSetLoader {
7
8 @Override
9 protected IDataSet createDataSet(Resource resource) throws Exception {
10 IDataSet flatXmlDataSet = super.createDataSet(resource);
11 return createReplacementDataSet(flatXmlDataSet);
12 }
13
14 private IDataSet createReplacementDataSet(IDataSet flatXmlDataSet) {
15 ReplacementDataSet replacementDataSet =
16 new ReplacementDataSet(flatXmlDataSet);
17
18 replacementDataSet.addReplacementObject("[empty_string]", "");
19 replacementDataSet.addReplacementObject("[null]", null);
20
21 return replacementDataSet;
22 }
23}
We have now created our custom data set loader class. Our next step is to ensure that the DbUnitTestExecutionListener uses our custom data set loader class.
If we want to use a custom data set loader, we have to configure the used data set loader class by annotating our integration test class with the @DbUnitConfiguration annotation.
After we have configured our custom data set loader, the source code of our test class looks as follows:
1import com.github.springtestdbunit.DbUnitTestExecutionListener;
2import com.github.springtestdbunit.annotation.DatabaseSetup;
3import com.github.springtestdbunit.annotation.DbUnitConfiguration;
4import org.junit.Test;
5import org.junit.experimental.categories.Category;
6import org.junit.runner.RunWith;
7import org.springframework.beans.factory.annotation.Autowired;
8import org.springframework.test.context.ContextConfiguration;
9import org.springframework.test.context.TestExecutionListeners;
10import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
11import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
12import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
13
14import java.util.Optional;
15
16import static org.assertj.core.api.Assertions.assertThat;
17
18@RunWith(SpringJUnit4ClassRunner.class)
19@ContextConfiguration(classes = {PersistenceContext.class})
20@TestExecutionListeners({
21 DependencyInjectionTestExecutionListener.class,
22 TransactionalTestExecutionListener.class,
23 DbUnitTestExecutionListener.class
24})
25@DatabaseSetup("messages.xml")
26@DbUnitConfiguration(dataSetLoader = ReplacementDataSetLoader.class)
27@Category(IntegrationTest.class)
28public class FindMessageByIdTest {
29
30 @Autowired
31 private MessageRepository repository;
32
33 @Test
34 public void shouldReturnOptionalThatContainsMessageWhenMessageIsFound() {
35 Optional<Message> found = repository.findById(Messages.HelloWorld.ID);
36 assertThat(found).isNotEmpty();
37 }
38}
When we run our integration test and use a data set that tries to insert an empty string to our database, our integration test fails and we see the following error message:
1java.lang.IllegalArgumentException: table.column=messages.MESSAGE_TEXT value
2is empty but must contain a value (to disable this feature check, set
3DatabaseConfig.FEATURE_ALLOW_EMPTY_FIELDS to true)
The source code of our DbUnit data set looks as follows:
1<dataset>
2 <messages id="1"
3 message_text="Hello World!"/>
4 <messages id="2"
5 message_text="[empty_string]"/>
6</dataset>
Let’s fix this problem by overriding the default configuration of DbUnit.
Modifying the Default Configuration of DbUnit
When we want change the default configuration of DbUnit, we have to create a new application context configuration class by following these steps:
First, we have to create an application context configuration class called IntegrationTestContext and ensure that this class imports the application context configuration class of our application.
After we have created the IntegrationTestContext class, its source code looks as follows:
1import org.springframework.context.annotation.Configuration;
2import org.springframework.context.annotation.Import;
3
4@Configuration
5@Import({PersistenceContext.class})
6public class IntegrationTestContext {
7
8}
Second, we have to implement a method that configures the DatabaseConfigBean bean which allows us to modify the used DbUnit configuration. Because we want to ensure that DbUnit allows us to use empty strings in INSERT statements, we have to set the value of the allowEmptyFields configuration option to true.
After we have configured the DatabaseConfigBean bean, the source code of the IntegrationTestContext class looks as follows:
1import org.springframework.context.annotation.Bean;
2import org.springframework.context.annotation.Configuration;
3import org.springframework.context.annotation.Import;
4
5@Configuration
6@Import({PersistenceContext.class})
7public class IntegrationTestContext {
8
9 @Bean
10 DatabaseConfigBean databaseConfigBean() {
11 DatabaseConfigBean config = new DatabaseConfigBean();
12 config.setAllowEmptyFields(true);
13 return config;
14 }
15}
Third, We have to implement a method that configures the DatabaseDataSourceConnectionFactoryBean which provides database connections for DbUnit. This method must be called: dbUnitDatabaseConnection(), and it takes the DataSource and DatabaseConfigBean beans as method parameters.
We can implement this method by creating a new DatabaseDataSourceConnectionFactoryBean object and returning the created object.
After we have configured the DatabaseDataSourceConnectionFactoryBean bean, the source code of the IntegrationTestContext class looks as follows:
1import com.github.springtestdbunit.bean.DatabaseConfigBean;
2import com.github.springtestdbunit.bean.DatabaseDataSourceConnectionFactoryBean;
3import org.springframework.context.annotation.Bean;
4import org.springframework.context.annotation.Configuration;
5import org.springframework.context.annotation.Import;
6
7import javax.sql.DataSource;
8
9@Configuration
10@Import({PersistenceContext.class})
11public class IntegrationTestContext {
12
13 @Bean
14 DatabaseConfigBean databaseConfigBean() {
15 DatabaseConfigBean config = new DatabaseConfigBean();
16 config.setAllowEmptyFields(true);
17 return config;
18 }
19
20 @Bean
21 DatabaseDataSourceConnectionFactoryBean dbUnitDatabaseConnection(DataSource dataSource,
22 DatabaseConfigBean databaseConfigBean) {
23 DatabaseDataSourceConnectionFactoryBean cf = new DatabaseDataSourceConnectionFactoryBean();
24 cf.setDataSource(dataSource);
25 cf.setDatabaseConfig(databaseConfigBean);
26 return cf;
27 }
28}
We have now created the required Spring beans. Next we have to ensure that our integration test class uses our new configuration.
Because the name of the DatabaseDataSourceConnectionFactoryBean is dbUnitDatabaseConnection, we don’t have to make any changes to the attributes of the @DbUnitConfiguration annotation. This means that we have to only change the application context configuration class that configures the application context of our integration tests.
After we have made the required change, the source code of our integration test class looks as follows:
1import com.github.springtestdbunit.DbUnitTestExecutionListener;
2import com.github.springtestdbunit.annotation.DatabaseSetup;
3import com.github.springtestdbunit.annotation.DbUnitConfiguration;
4import org.junit.Test;
5import org.junit.experimental.categories.Category;
6import org.junit.runner.RunWith;
7import org.springframework.beans.factory.annotation.Autowired;
8import org.springframework.test.context.ContextConfiguration;
9import org.springframework.test.context.TestExecutionListeners;
10import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
11import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
12import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
13
14import java.util.Optional;
15
16import static org.assertj.core.api.Assertions.assertThat;
17
18@RunWith(SpringJUnit4ClassRunner.class)
19@ContextConfiguration(classes = {IntegrationTestContext.class})
20@TestExecutionListeners({
21 DependencyInjectionTestExecutionListener.class,
22 TransactionalTestExecutionListener.class,
23 DbUnitTestExecutionListener.class
24})
25@DatabaseSetup("messages.xml")
26@DbUnitConfiguration(dataSetLoader = ReplacementDataSetLoader.class)
27@Category(IntegrationTest.class)
28public class FindMessageByIdTest {
29
30 @Autowired
31 private MessageRepository repository;
32
33 @Test
34 public void shouldReturnOptionalThatContainsFoundMessageWhenMessageIsFound() {
35 Optional<Message> found = repository.findById(Messages.HelloWorld.ID);
36 assertThat(found).isNotEmpty();
37 }
38}
If you are using Spring Boot, you can configure the used application context configuration class by using the @SpringBootTest annotation:
1@SpringBootTest(classes = {IntegrationTestContext.class})
When we run our integration tests, we notice that they pass.
Let’s summarize what we learned from this lesson.
Summary
This lesson has taught us three things:
- The
@DbUnitConfigurationannotation allows us to modify the configuration ofDbUnitTestExecutionListener. - If we want to support replacement data sets, we have to create a new data set loader that transforms our original data sets into
ReplacementDataSetobjects. - If we want to modify the used DbUnit configuration, we have to create a new
DatabaseConfigBeanbean.