In a previous post, we saw how to create Spring Data JPA repositories, and how to create in-memory versions of these repositories, useful for testing other code that uses repositories.
Obviously, the repositories themselves need to be tested. DbUnit can be used to populate a database with test data so that we know the state of the database when we run the tests. DbUnit reads XML files with the contents of database tables and inserts the data in the database before each test.
We also need to test the in-memory versions of the repositories, and we will see that we can use the same test cases, and the same DbUnit files, to test the in-memory repositories.
Getting Started
There is a sample project, rld-repositories-sample
, that shows how the code here fits together, including Spring configuration. See the previous post for instructions.
Example Code
In the previous post, we created repositories for working with departments and employees. Now, let’s create DbUnit files containing test data:
department.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE dataset SYSTEM "rld-repositories-sample.dtd"> <dataset> <DEPARTMENT ID="1" NAME="IT" /> <DEPARTMENT ID="2" NAME="Sales" /> </dataset>
employee.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE dataset SYSTEM "rld-repositories-sample.dtd"> <dataset> <EMPLOYEE ID="1" FIRST_NAME="Jack" LAST_NAME="Bauer" SALARY="100000" DEPARTMENT_ID="1" /> <EMPLOYEE ID="2" FIRST_NAME="Chloe" LAST_NAME="O'Brian" SALARY="80000" DEPARTMENT_ID="2" /> <EMPLOYEE ID="3" FIRST_NAME="Kim" LAST_NAME="Bauer" SALARY="120000" DEPARTMENT_ID="1" /> <EMPLOYEE ID="4" FIRST_NAME="David" LAST_NAME="Palmer" SALARY="180000" DEPARTMENT_ID="2" /> <EMPLOYEE ID="5" FIRST_NAME="Michelle" LAST_NAME="Dessler" SALARY="90000" DEPARTMENT_ID="1" /> </dataset>
The DbUnit XML files use a DTD that can be automatically created using the DbUnitDtdGenerator
class from rld-build-tools
:
rld-repositories-sample.dtd
<!ELEMENT dataset ( DEPARTMENT*, EMPLOYEE*)> <!ELEMENT DEPARTMENT EMPTY> <!ATTLIST DEPARTMENT ID CDATA #REQUIRED NAME CDATA #REQUIRED > <!ELEMENT EMPLOYEE EMPTY> <!ATTLIST EMPLOYEE ID CDATA #REQUIRED FIRST_NAME CDATA #REQUIRED LAST_NAME CDATA #REQUIRED SALARY CDATA #REQUIRED DEPARTMENT_ID CDATA #REQUIRED >
We can now create integration tests that connect to a real database and use DbUnit to add the data from the XML files to make sure the database is in a known state before each test. This is simplified by using the base class AbstractDbTest
from rld-build-tools
. In the constructor, you provide the document type (DTD) and the XML files you want to read. You must also provide a data source by overriding the getDataSource()
method.
Testing the JpaDepartmentRepository
is straightforward:
JpaDepartmentRepositoryIT.java
package com.reallifedeveloper.sample.infrastructure.persistence; // imports... @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = { "classpath:META-INF/spring-context-rld-repositories-sample-test.xml" }) public class JpaDepartmentRepositoryIT extends AbstractDbTest { @Autowired private JpaDepartmentRepository repository; @Autowired private DataSource ds; public JpaDepartmentRepositoryIT() { super(null, "/dbunit/rld-repositories-sample.dtd", "/dbunit/department.xml"); } @Test public void findByExistingName() { Department department = repository().findByName("IT"); assertThat(department, notNullValue()); assertThat(department.name(), is("IT")); assertThat(department.id(), is(1L)); } // Other test methods... protected JpaDepartmentRepository repository() { return repository; } @Override protected DataSource getDataSource() { return ds; } }
Note the repository()
method that is used to access the repository being tested. This will prove useful later, when we create a sub-class for testing the in-memory version of the repository.
Testing the JpaEmployeeRepository
is similar, but in this case we use test data for both departments and employees:
JpaEmployeeRepositoryIT.java
package com.reallifedeveloper.sample.infrastructure.persistence; // imports... @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = { "classpath:META-INF/spring-context-rld-repositories-sample-test.xml" }) public class JpaEmployeeRepositoryIT extends AbstractDbTest { private static final BigDecimal DELTA = new BigDecimal(0.0000001); @Autowired private JpaEmployeeRepository repository; @Autowired private JpaDepartmentRepository departmentRepository; @Autowired private DataSource ds; private Department departmentSales; public JpaEmployeeRepositoryIT() { super(null, "/dbunit/rld-repositories-sample.dtd", "/dbunit/department.xml", "/dbunit/employee.xml"); } @Before public void init() { departmentSales = departmentRepository().findByName("Sales"); if (departmentSales == null) { fail("Error in test data: department 'Sales' not found"); } } @Test public void findByExistingId() { Employee employee = repository().findById(1L); assertThat(employee, notNullValue()); assertThat(employee.firstName(), is("Jack")); assertThat(employee.lastName(), is("Bauer")); assertThat(employee.salary(), closeTo(new BigDecimal(100000), DELTA)); assertThat(employee.department().name(), is("IT")); } // Other test methods... protected JpaEmployeeRepository repository() { return repository; } protected JpaDepartmentRepository departmentRepository() { return departmentRepository; } @Override protected DataSource getDataSource() { return ds; } }
We previously created in-memory versions of the repositories, to use when testing other code that uses the repositories. We obviously need to test these implementations as well, so that we can trust them when they are used in other tests.
It is easy to create a sub-class of the integration tests and plug in in-memory implementations for the repositories, that is what the protected repository()
method is for. The question is how we populate the repositories with the test data from the DbUnit XML files.
The DbUnitFlatXmlReader
class from rld-build-tools
can be used to read DbUnit XML files and populate Spring Data JPA repositories, including our in-memory implementations. With this, it is easy to create tests for our in-memory repositories:
InMemoryDepartmentRepositoryTest.java
package com.reallifedeveloper.sample.test; // imports... @RunWith(JUnit4.class) public class InMemoryDepartmentRepositoryTest extends JpaDepartmentRepositoryIT { private InMemoryDepartmentRepository repository = new InMemoryDepartmentRepository(); @Override public void setUpDatabase() throws Exception { DbUnitFlatXmlReader xmlReader = new DbUnitFlatXmlReader(); xmlReader.read("/dbunit/department.xml", repository, Department.class, Long.class); } @Override public void tearDownDatabase() throws Exception { // Do nothing } @Override protected JpaDepartmentRepository repository() { return repository; } }
InMemoryEmployeeRepositoryTest.java
package com.reallifedeveloper.sample.test; // imports... @RunWith(JUnit4.class) public class InMemoryEmployeeRepositoryTest extends JpaEmployeeRepositoryIT { private InMemoryEmployeeRepository repository = new InMemoryEmployeeRepository(); private InMemoryDepartmentRepository departmentRepository = new InMemoryDepartmentRepository(); @Override public void setUpDatabase() throws Exception { DbUnitFlatXmlReader xmlReader = new DbUnitFlatXmlReader(); xmlReader.read("/dbunit/department.xml", departmentRepository, Department.class, Long.class); xmlReader.read("/dbunit/employee.xml", repository, Employee.class, Long.class); } @Override public void tearDownDatabase() throws Exception { // Do nothing } @Override protected JpaEmployeeRepository repository() { return repository; } @Override protected JpaDepartmentRepository departmentRepository() { return departmentRepository; } }
Notes
- It is difficult to measure code coverage for the Spring Data JPA repositories since the actual classes are created dynamically. We can use the tests for the in-memory repositories as a substitute: run a code coverage tool on the
InMemoryEmployeeRepositoryTest
, for example, to see that the tests cover all ofInMemoryEmployeeRepository
. If they do, you know that the testJpaEmployeeRepositoryIT
covers all the methods ofEmployeeRepository
. - Using
DbUnitFlatXmlReader
to populate repositories can be useful to insert reference data into in-memory repositories that are used when testing other code.
Conclusion
Using AbstractDbTest
as a base class for your database tests is an easy way to use DbUnit. The document type for the XML files can be generated using DbUnitDtdGenerator
.
With DbUnitFlatXmlReader
, it is possible to use DbUnit XML files to populate Spring Data JPA repositories, including our in-memory implementations.
- Receiving Urgent Market Message Push Notifications from Nord Pool - November 1, 2018
- Zen and the Art of Computer Programming - September 2, 2017
- Reading JSON Files to Create Test Versions of REST Clients - April 8, 2017