Testing Spring Data JPA and In-Memory Repositories Using DbUnit

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 of InMemoryEmployeeRepository. If they do, you know that the test JpaEmployeeRepositoryIT covers all the methods of EmployeeRepository.
  • 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.

RealLifeDeveloper

Published by

RealLifeDeveloper

I'm a software developer with 20+ years of experience who likes to work in agile teams using Specification by Example, Domain-Driven Design, Continuous Delivery and lots of automation.

Leave a Reply

Your email address will not be published. Required fields are marked *