Contract Tests - Parameterised Test Cases
Tags: tdd, unittest, testing, softwaredesign
Published: 2023-06-28 09:10:39


This is the final installment of a three-part series about contract tests. In the [first blog post](https://principal-it.eu/2023/02/contract-tests/) we’ve discussed the rationale behind contract tests. Next we’ve looked at how to implement contract tests using [Abstract Test Cases](https://principal-it.eu/2023/04/contract-tests-abstract-test-cases/). In this blog post, we’re going to look into an alternative approach to _Abstract Test Cases_ by using [parameterised tests](https://en.wikipedia.org/wiki/Data-driven_testing) instead.

With this alternative approach, we no longer rely on inheritance, where concrete classes are derived from an abstract base class that contains the test cases. Instead, Sociable tests are added to a regular test class, just as we would normally do. We apply the “Abstract Factory” design pattern to create the respective Subject Under Test, either the real implementation or the fake implementation. Then we use this factory to execute every parameterised test case for each subject that the factory instantiates.

Let’s have a look at an example to demonstrate this approach. 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. …

We’re going to implement the same example as we’ve used in the [previous blog post](https://principal-it.eu/2023/04/contract-tests-abstract-test-cases/). The Subject Under Test is still a repository for storing and retrieving employee data to and from a database.

We start out by defining an interface.

```java
interface EmployeeRepositoryStrategy extends AutoCloseable {
    EmployeeRepository getSubjectUnderTest();
}
```

This interface defines a method for creating the Subject Under Test, which in our example is an instance of an `EmployeeRepository`. This interface also extends the `AutoCloseable` interface which provides a `close` method. This method can be used to perform some cleanup.

The following piece of code shows the implementation of the `SQLiteEmployeeRepositoryStrategy`, which implements the `EmployeeRepositoryStrategy` interface.

```java
static class SQLiteEmployeeRepositoryStrategy implements EmployeeRepositoryStrategy {

    private final NamedParameterJdbcTemplate jdbcTemplate;
    private final SQLiteEmployeeRepository sqliteEmployeeRepository;

    public SQLiteEmployeeRepositoryStrategy() {
        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
    public EmployeeRepository getSubjectUnderTest() {
        return sqliteEmployeeRepository;
    }

    @Override
    public void close() {
        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 `close` method simply removes all records from the `Employee` table in the database.

The following piece of code shows the implementation of the `FakeEmployeeRepositoryStrategy`, which also implements the `EmployeeRepositoryStrategy` interface.

```java
static class FakeEmployeeRepositoryStrategy implements EmployeeRepositoryStrategy {

    private final FakeEmployeeRepository fakeEmployeeRepository;

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

    @Override
    public EmployeeRepository getSubjectUnderTest() {
        return fakeEmployeeRepository;
    }

    @Override
    public void close() {
        fakeEmployeeRepository.clear();
    }
}

```

The constructor initialises an instance of the `FakeEmployeeRepository`. This instance is also returned by the implementation of the `getSubjectUnderTest` method. The `close` method removes all the data from the repository by calling the `clear` method.

The contract tests for the `EmployeeRepository` now look like this:

```java
public class EmployeeRepositoryTests {

    @ParameterizedTest
    @ArgumentsSource(EmployeeRepositoryStrategyProvider.class)
    public void Should_return_nothing_for_a_non_existing_employee(EmployeeRepositoryStrategy strategy) {

        var unknownId = UUID.fromString("753350fb-d9a2-4e4b-8ca4-c969ca54ef5f");

        var SUT = strategy.getSubjectUnderTest();
        var retrievedEmployee = SUT.get(unknownId);

        assertThat(retrievedEmployee).isNull();
    }

    @ParameterizedTest
    @ArgumentsSource(EmployeeRepositoryStrategyProvider.class)
    public void Should_return_employee_for_identifier(EmployeeRepositoryStrategy strategy) {

        var SUT = strategy.getSubjectUnderTest();

        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);
    }

    @ParameterizedTest
    @ArgumentsSource(EmployeeRepositoryStrategyProvider.class)
    public void Should_save_employee(EmployeeRepositoryStrategy strategy) {

        var SUT = strategy.getSubjectUnderTest();

        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);
    }
}
```

Here we have three parameterised tests. Each test receives a particular `EmployeeRepositoryStrategy` instance when it gets executed. The tests themselves interact with the SUT through this specified interface. After the test has been executed, the test runner will automatically call the `close` method as it recognises that the parameter implements the `AutoCloseable` interface.

Notice that we provide an instance of the `EmployeeRepositoryStrategyProvider` as an argument source. The implementation of this _factory_ class looks like this:

```java
static class EmployeeRepositoryStrategyProvider implements ArgumentsProvider {

    @Override
    public Stream<? extends Arguments> provideArguments(ExtensionContext extensionContext) {
        return Stream.of(
            Arguments.of(new SQLiteEmployeeRepositoryStrategy()),
            Arguments.of(new FakeEmployeeRepositoryStrategy())
        );
    }
}
```

The `provideArguments` method simply creates and returns an instance of the `SQLiteEmployeeRepositoryStrategy` class and the `FakeEmployeeRepositoryStrategy` respectively. Applying this provider as the parameter source ensures that each test is executed twice; once for the `SQLiteEmployeeRepository` and once for the `FakeEmployeeRepository`. The test runner will therefore execute six tests in total.

The advantage of using this approach is that we use [composition over class inheritance](http://en.wikipedia.org/wiki/Composition_over_inheritance) as we no longer rely on subclasses. A disadvantage to this approach is that it’s slightly more complicated compared to _Abstract Test Cases_, which can be a debatable subject.

To conclude, I would like to thank [Mario Pio Gioiosa](https://reversecoding.net/) for teaching me about the [Parameterised Test Cases](https://gist.github.com/mariopiogioiosa/0b74a80f2bd687d1926134b943ed809e) approach.