Contract Tests - Abstract Test Cases
Tags: tdd, unittest, testing, softwaredesign
Published: 2023-04-12 13:12:33

In the previous blog post, we’ve discussed the rationale behind [contract tests](https://principal-it.eu/2023/02/contract-tests/). They are used for exercising those parts of an application that communicate with other parts of the system by crossing the process boundary. In this blog post, we’re going to have a look at how to implement contract tests in practice. The most common approach you’ll likely encounter is [Abstract Test Cases](https://wiki.c2.com/?AbstractTestCases).

With this approach, we write Sociable tests as one normally would write them. However, instead of adding them to a regular test class we’ll add them to an abstract base class instead. Then we derive a subclass for each implementation. A subclass thereby inherits all the test cases from the base class. In essence, this is the “Template Method” design pattern in action, where each test becomes a template method. Concrete subclasses implement primitive operation methods for creating and interacting with their respective Subject Under Test, either the real implementation or the fake implementation.

Let’s have a look at an example to demonstrate this. Although we’ve used Java in our example, the same pattern can be implemented in a similar way by using other object-oriented languages like C#, Python, Ruby, etc. …

The following example shows a Subject Under Test that is a repository for storing and retrieving employee data to and from a database. The contract tests for the `EmployeeRepository` look like this:

```java
abstract class EmployeeRepositoryTests {

    private EmployeeRepository SUT;

    @BeforeEach
    public void setUp() {
        SUT = getSubjectUnderTest();
    }

    @AfterEach
    public void tearDown() {
        cleanup();
    }

    @Test
    public void Should_return_nothing_for_non_existing_employee() {

        var unknownId = UUID.fromString("753350fb-d9a2-4e4b-8ca4-c969ca54ef5f");
        var retrievedEmployee = SUT.get(unknownId);
        assertThat(retrievedEmployee).isNull();
    }

    @Test
    public void Should_return_employee_for_identifier() {

        var employee = new Employee(
            UUID.fromString("13e420a7-3bfd-4c6b-adde-d673c6ee1469"),
            "Dwight", "Schrute",
            LocalDate.of(1966, 1, 20));
        SUT.save(employee);

        var retrievedEmployee = SUT.get(UUID.fromString("13e420a7-3bfd-4c6b-adde-d673c6ee1469"));
        assertThat(retrievedEmployee).usingRecursiveComparison().isEqualTo(employee);
    }

    @Test
    public void Should_save_employee() {

        var newEmployee = new Employee(
            UUID.fromString("55674e0b-4a1f-4cd1-be96-bcdc67fd4ded"),
            "Dwight", "Schrute",
            LocalDate.of(1966, 1, 20));
        SUT.save(newEmployee);

        var persistedEmployee = SUT.get(UUID.fromString("55674e0b-4a1f-4cd1-be96-bcdc67fd4ded"));
        assertThat(persistedEmployee).usingRecursiveComparison().isEqualTo(newEmployee);
    }

    abstract EmployeeRepository getSubjectUnderTest();
    abstract void cleanup();
}
```

Notice that the `EmployeeRepositoryTests` class is abstract. It also defines two abstract methods: `getSubjectUnderTest` and `cleanup`. These abstract methods are being called by the `setUp` and `tearDown` method respectively, which in turn are executed before and after each test. The tests themselves interact with the SUT through the `EmployeeRepository`interface.

```java
public interface EmployeeRepository {
    Employee get(UUID id);
    void save(Employee employee);
}
```

We’ve derived two concrete classes from `EmployeeRepositoryTests`, one for the _real_ repository implementation (`SQLiteEmployeeRepository`) and one for the _fake_ repository implementation (`FakeEmployeeRepository`). Both of these implement the `EmployeeRepository` interface.

The following piece of code shows the implementation of the `SQLiteEmployeeRepositoryTests`, which exercises the code of the `SQLiteEmployeeRepository`.

```java
class SQLiteEmployeeRepositoryTests extends EmployeeRepositoryTests {

    private final NamedParameterJdbcTemplate jdbcTemplate;
    private final SQLiteEmployeeRepository sqliteEmployeeRepository;

    public SQLiteEmployeeRepositoryTests() {
        var database = getClass().getClassLoader().getResource("database.db");
        var connectionUrl = String.format("jdbc:sqlite:%s", database);

        var sqliteDataSource = new SQLiteDataSource();
        sqliteDataSource.setUrl(connectionUrl);

        this.jdbcTemplate = new NamedParameterJdbcTemplate(sqliteDataSource);
        this.sqliteEmployeeRepository = new SQLiteEmployeeRepository(jdbcTemplate);
    }

    @Override
    EmployeeRepository getSubjectUnderTest() {
        return sqliteEmployeeRepository;
    }

    @Override
    void cleanup() {
        jdbcTemplate.getJdbcOperations().execute("DELETE FROM Employee");
    }
}
```

The constructor initialises an instance of the `SQLiteEmployeeRepository`. This instance is returned by the implementation of the `getSubjectUnderTest` method. The `cleanup` method simply removes all records from the `Employee`table in the database.

The implementation of the `SQLiteEmployeeRepository` itself looks like this:

```java
public class SQLiteEmployeeRepository implements EmployeeRepository {

    private final NamedParameterJdbcTemplate jdbcTemplate;

    public SQLiteEmployeeRepository(NamedParameterJdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    @Override
    public Employee get(UUID id) {
        var sql = "SELECT Id, FirstName, LastName, BirthDate " +
            "FROM Employee " +
            "WHERE Id = :id";

        var parameters = Map.of("id", id);

        try {
            return jdbcTemplate
                .queryForObject(sql, parameters, SQLiteEmployeeRepository::mapEmployeeFromResultSet);
        } catch (EmptyResultDataAccessException e) {
            return null;
        }
    }

    private static Employee mapEmployeeFromResultSet(ResultSet resultSet, int rowNumber) 
        throws SQLException {

        return new Employee(
            UUID.fromString(resultSet.getString("Id")),
            resultSet.getString("FirstName"),
            resultSet.getString("LastName"),
            LocalDate.parse(resultSet.getString("BirthDate"))
        );
    }

    @Override
    public void save(Employee employee) {
        var sql = "INSERT INTO Employee (Id, FirstName, LastName, BirthDate) " +
            "VALUES(:id, :firstName, :lastName, :birthDate)";

        var parameters = Map.of(
            "id", employee.getId(),
            "firstName", employee.getFirstName(),
            "lastName", employee.getLastName(),
            "birthDate", employee.getBirthDate());

        jdbcTemplate.update(sql, parameters);
    }
}
```

The following piece of code shows the implementation of the `FakeEmployeeRepositoryTests`, which exercises the code of the `FakeEmployeeRepository`.

```java
public class FakeEmployeeRepositoryTests extends EmployeeRepositoryTests {

    private final FakeEmployeeRepository fakeEmployeeRepository;

    public FakeEmployeeRepositoryTests() {
        fakeEmployeeRepository = new FakeEmployeeRepository();
    }

    @Override
    EmployeeRepository getSubjectUnderTest() {
        return fakeEmployeeRepository;
    }

    @Override
    void cleanup() {
        fakeEmployeeRepository.clear();
    }
}
```

Again, the constructor initialises an instance of the `FakeEmployeeRepository`. This instance is also returned by the implementation of the `getSubjectUnderTest` method. The `cleanup` method removes all data from the repository by calling the `clear` method. This method, in turn, simply clears the internal `employees` map as shown by the code of the `FakeEmployeeRepository`.

```java
public class FakeEmployeeRepository implements EmployeeRepository {

    private final Map<UUID, Employee> employees;

    public FakeEmployeeRepository() {
        employees = new HashMap<>();
    }

    @Override
    public Employee get(UUID id) {
        return employees.get(id);
    }

    @Override
    public void save(Employee employee) {
        employees.put(employee.getId(), employee);
    }

    public void clear() {
        employees.clear();
    }
}
```

That’s it. When running the `EmployeeRepositoryTests`, the test runner will execute six tests; three tests for the `SQLiteEmployeeRepository` and the same three tests for the `FakeEmployeeRepository`.

To conclude, _Abstract Test Cases_ are a quick and easy way to get started with contract tests. However, as the Gang of Four already expressed in their well-known book [Design Patterns](https://www.goodreads.com/book/show/85009.Design_Patterns), we should favour composition over class inheritance. Following this principle brings us to another approach for implementing contract tests, which we’re going to discuss in the next blog post.