Example of role tests in Java with Junit
Tags: programming, beginners, tdd, webdev
Published: 2023-01-18 17:14:03

---
title: Example of role tests in Java with Junit
published: true
description: 
tags: 
  - programming
  - beginners
  - tdd
  - webdev
canonical_url: https://codesai.com/posts/2022/08/role-tests-junit
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u7b26xjaft6ys4p500y1.png
# Use a ratio of 100:42 for best results.
# published_at: 2023-01-18 17:02 +0000
---

I’d like to continue with the topic of role tests that we wrote about in a [previous post](https://dev.to/trikitrok/role-tests-for-interfaces-discovered-through-tdd-4e9l), by showing an example of how it can be applied in Java to reduce duplication in your tests.

This example comes from a deliberate practice session I did recently with some people from Women Tech Makers Barcelona with whom I’m doing [Codesai’s Practice Program in Java](https://github.com/Codesai/practice_program) twice a month.

Making additional changes to the code that resulted from solving the [Bank Kata](https://kata-log.rocks/banking-kata) we wrote the following tests to develop two different implementations of the `TransactionsRepository` port: the `InMemoryTransactionsRepository` and the `FileTransactionsRepository`.

These are their tests, respectively:

```java
package bank.tests.integration;

// some ommited imports...

public class InMemoryTransactionsRepositoryTest {

  private TransactionsRepository repository;
  private List<Transaction> initialTransactions;

  @Before
  public void setup() throws ParseException {
    repository = new InMemoryTransactionsRepository();
    initialTransactions = Arrays.asList(
        aTransaction().withDeposit(100).on("10/10/2021").build(),
        aTransaction().withWithdrawal(50).on("15/10/2021").build());
    prepareData(initialTransactions);
  }

  @Test
  public void a_transaction_can_be_saved() throws ParseException {
    Transaction transaction = aTransaction().withDeposit(500).on("25/10/2021").build();

    repository.save(transaction);

    assertThat(repository.retrieveAll(), is(addTo(initialTransactions, transaction)));
  }

  @Test
  public void transactions_can_be_retrieved() {
    assertThat(repository.retrieveAll(), is(initialTransactions));
  }

  private void prepareData(List<Transaction> transactions) {
    for (Transaction transaction : transactions) {
      repository.save(transaction);
    }
  }
}
```
```java
package bank.tests.integration;

// some ommited imports...

public class FileTransactionsRepositoryTest {
  private static final String TRANSACTIONS_FILE = "src/test/resources/initial_transactions.txt";

  private TransactionsFile transactionsFile;
  private TransactionsRepository repository;
  private List<Transaction> initialTransactions;

  @Before
  public void setup() throws ParseException {
    transactionsFile = new TransactionsFile(TRANSACTIONS_FILE);
    repository = new FileTransactionsRepository(TRANSACTIONS_FILE);
    initialTransactions = Arrays.asList(
        aTransaction().withDeposit(100).on("10/10/2021").build(),
        aTransaction().withWithdrawal(50).on("15/10/2021").build());
    prepareData(initialTransactions);
  }

  @Test
  public void a_transaction_can_be_saved() throws ParseException {
    Transaction transaction = aTransaction().withDeposit(500).on("25/10/2022").build();

    repository.save(transaction);

    assertThat(repository.retrieveAll(), is(addTo(initialTransactions, transaction)));
  }

  @Test
  public void transactions_can_be_retrieved() {
    assertThat(repository.retrieveAll(), is(initialTransactions));
  }

  private void prepareData(List<Transaction> transactions) {
    try {
      transactionsFile.clean();
      transactionsFile.append(transactions);
    } catch (IOException e) {
      System.err.println("Error preparing transaction data in tests: " + e);
    }
  }
}
```

As you can see both tests contain the same test cases: `a_transaction_can_be_saved` and `transactions_can_be_retrieved` but their implementations are different for each class. This makes sense because both implementations implement the same role, (see our [previous post](https://codesai.com/posts/2022/04/role-tests) to learn how this relates to [Liskov Substitution Principle](https://en.wikipedia.org/wiki/Liskov_substitution_principle)).

We can make this fact more explicit by using *role tests*. In this case, [Junit](https://junit.org/junit5/) does not have something equivalent or similar to the [RSpec’s *shared examples* functionality](https://relishapp.com/rspec/rspec-core/v/3-10/docs/example-groups/shared-examples) we used in our [previous example in Ruby](https://codesai.com/posts/2022/04/role-tests). Nonetheless, we can apply the [Template Method pattern](https://en.wikipedia.org/wiki/Template_method_pattern) to write the *role test*, so that we remove the duplication, and more importantly make the contract we are implementing more explicit.

To do that we created an abstract class, `TransactionsRepositoryRoleTest`. This class contains the tests cases that document the role and protect its contract (`a_transaction_can_be_saved` and `transactions_can_be_retrieved`) and defines *hooks* for the operations that will vary in the different implementations of this integration test
(`prepareData`, `readAllTransactions` and `createRepository`):

```java
package bank.tests.integration.roles;

// some ommited imports...

public abstract class TransactionsRepositoryRoleTest {
  protected TransactionsRepository repository;
  private List<Transaction> initialTransactions;

  @Before
  public void setup() throws ParseException {
    repository = createRepository();
    initialTransactions = Arrays.asList(
        aTransaction().withDeposit(100).on("10/10/2021").build(),
        aTransaction().withWithdrawal(50).on("15/10/2021").build());
    prepareData(initialTransactions);
  }

  @Test
  public void a_transaction_can_be_saved() throws ParseException {
    Transaction transaction = aTransaction().withDeposit(500).on("25/10/2021").build();

    repository.save(transaction);

    assertThat(readAllTransactions(), is(add(initialTransactions, transaction)));
  }

  @Test
  public void transactions_can_be_retrieved() {
    assertThat(repository.retrieveAll(), is(initialTransactions));
  }

  protected abstract TransactionsRepository createRepository();

  protected abstract List<Transaction> readAllTransactions();

  protected abstract void prepareData(List<Transaction> transactions);

  private List<Transaction> add(List<Transaction> transactions, Transaction transaction) {
    List<Transaction> result = new ArrayList<>(transactions);
    result.add(transaction);
    return result;
  }
}
```

Then we made the previous tests extend `TransactionsRepositoryRoleTest` and implemented the *hooks*. 

This is the new code of `InMemoryTransactionsRepositoryTest`:
```java
package bank.tests.integration;

// some ommited imports...

public class InMemoryTransactionsRepositoryTest extends TransactionsRepositoryRoleTest {

  @Override
  protected TransactionsRepository createRepository() {
    return new InMemoryTransactionsRepository();
  }

  @Override
  protected void prepareData(List<Transaction> initialTransactions) {
    for (Transaction transaction : initialTransactions) {
      repository.save(transaction);
    }
  }

  @Override
  protected List<Transaction> readAllTransactions() {
    return repository.retrieveAll();
  }
}
```

And this is the new code of `FileTransactionsRepositoryTest` after the refactoring:
```java
package bank.tests.integration;

// some ommited imports...

public class FileTransactionsRepositoryTest extends TransactionsRepositoryRoleTest {
  private static final String TRANSACTIONS_FILE = "src/test/resources/initial_transactions.txt";
  private final TransactionsFile transactionsFile;

  public FileTransactionsRepositoryTest() {
    transactionsFile = new TransactionsFile(TRANSACTIONS_FILE);
  }

  @Override
  protected TransactionsRepository createRepository() {
    return new FileTransactionsRepository(TRANSACTIONS_FILE);
  }

  @Override
  protected void prepareData(List<Transaction> transactions) {
    try {
      transactionsFile.clean();
      transactionsFile.append(transactions);
    } catch (IOException e) {
      System.err.println("Error preparing transaction data in tests: " + e);
    }
  }

  @Override
  protected List<Transaction> readAllTransactions() {
    List<Transaction> transactions = new ArrayList<>();
    try {
      transactions = transactionsFile.readTransactions();
    } catch (IOException | ParseException e) {
      System.err.println("Error reading transaction data in test: " + e);
    }
    return transactions;
  }
}
```

This new version of the tests not only reduces duplication, but also makes explicit and protects the behaviour of the `TransactionsRepository` role. It also makes less error prone the process of adding a new implementation of `TransactionsRepository` because just by extending the `TransactionsRepositoryRoleTest`, you’d get a checklist of the behaviour you need to implement to ensure substitutability, i.e., to ensure the  [Liskov Substitution Principle](https://en.wikipedia.org/wiki/Liskov_substitution_principle) is not violated.

Have a look at this [Jason Gorman](https://codemanship.wordpress.com/)’s [repository](https://github.com/jasongorman/ContractTesting) to see another example that applies the same technique.

In a future post we’ll show how we can do the same in JavaScript using [Jest](https://jestjs.io/).

<h2>Acknowledgements.</h2>

I’d like to thank the WTM study group, and especially [Inma Navas](https://twitter.com/InmaCNavas) and [Laura del Toro](https://www.linkedin.com/in/laura-del-toro-sosa/) for practising with this kata together.

Thanks to my Codesai colleagues, [Inma Navas](https://twitter.com/InmaCNavas) and [Laura del Toro](https://www.linkedin.com/in/laura-del-toro-sosa/) for reading the initial drafts and giving me feedback, and to [Esranur Kalay](https://www.pexels.com/es-es/@esranurkalay/) for the picture.

<h2>References.</h2>

* [Role tests for implementation of interfaces discovered through TDD
](https://dev.to/trikitrok/role-tests-for-interfaces-discovered-through-tdd-4e9l), [Manuel Rivero](https://twitter.com/trikitrok)
* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.goodreads.com/book/show/85009.Design_Patterns), [Erich Gamma](https://en.wikipedia.org/wiki/Erich_Gamma), [Ralph Johnson](http://software-pattern.org/Author/29), [John Vlissides](https://en.wikipedia.org/wiki/John_Vlissides), [Richard Helm](https://wiki.c2.com/?RichardHelm)
* [Liskov Substitution Principle](https://en.wikipedia.org/wiki/Liskov_substitution_principle)
* [101 Uses For Polymorphic Testing (Okay... Three)](http://codemanship.co.uk/parlezuml/blog/?postid=1183), [Jason Gorman](http://codemanship.co.uk/parlezuml/blog/)
* [Contract Testing example
 repository](https://github.com/jasongorman/ContractTesting), [Jason Gorman](https://codemanship.wordpress.com/)

Photo from [Esranur Kalay
 in Pexels](https://www.pexels.com/es-es/@esranurkalay/)