Role tests for interfaces discovered through TDD
Tags: programming, beginners, tdd, webdev
Published: 2022-12-27 18:54:37

---
title: "Role tests for interfaces discovered through TDD"
published: true
description: "Role tests for implementation of interfaces discovered through TDD"
canonical_url: https://codesai.com/posts/2022/04/role-tests
tags: 
  - programming
  - beginners
  - tdd
  - webdev
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2v1xayzqqc0q7pav5szo.jpg
# Use a ratio of 100:42 for best results.
# published_at: 2022-12-27 18:43 +0000
---

<h2>Introduction.</h2>

Working through the first three iterations of a [workshop's exercise](https://github.com/aleasoluciones/pycones2014), we produced several [application services](http://gorodinski.com/blog/2012/04/14/services-in-domain-driven-design-ddd/) that at some point collaborated with a users [repository](http://martinfowler.com/eaaCatalog/repository.html) that we hadn't yet created so we used a [test double](https://martinfowler.com/bliki/TestDouble.html) in its place in their tests.

These are the tests:

```ruby
require 'spec_helper'
require 'cos/actions/users/register_user'
require 'cos/core/users/errors'

describe Actions::RegisterUser do
  let(:users_repository) { double('UsersRepository') }
  let(:new_user_name) {'@foolano'}

  before do
    stub_const('Users::Repository', users_repository)
  end

  describe "Registering a user" do
    it "can register a user that is not already registered" do
      allow(users_repository).
        to receive(:registered?).
        with(new_user_name).and_return(false)
      expect(users_repository).
        to receive(:register).with(new_user_name)

      Actions::RegisterUser.do(new_user_name)
    end

    it "fails when trying to register a user that is already registered" do
      allow(users_repository).
        to receive(:registered?).with(new_user_name).and_return(true)

      expect{Actions::RegisterUser.do(new_user_name)}.
        to raise_error(Users::Errors::AlreadyRegistered)
    end
  end
end
```

```ruby
require 'spec_helper'
require 'cos/actions/users/follow_user'
require 'cos/core/users/errors'

describe Actions::FollowUser do

  let(:follower_name) { "foolano" }
  let(:followed_name) { "mengano" }
  let(:users_repository) { double('UsersRepository') }

  before do
    stub_const('Users::Repository', users_repository)
  end

  describe "Following a user" do
    describe "when both users are registered" do
      it "succesfully adds a follower to a followed user" do
        allow(users_repository).
          to receive(:registered?).with(follower_name).and_return(true)
        allow(users_repository).
          to receive(:registered?).with(followed_name).and_return(true)
        expect(users_repository).
          to receive(:add_follower).with(follower_name, followed_name)

        Actions::FollowUser.do follower_name, followed_name
      end
    end

    describe "when any of them is not registered" do
      it "raises an error when trying to add a registered follower to a followed user that does not exist" do
        allow(users_repository).
          to receive(:registered?).with(follower_name).and_return(true)
        allow(users_repository).
          to receive(:registered?).with(followed_name).and_return(false)

        expect {Actions::FollowUser.do follower_name, followed_name}.
          to raise_error(Users::Errors::NonRegistered)
      end

      it "raises an error when trying to add a follower that does not exist to a registered followed user" do
        allow(users_repository).
          to receive(:registered?).with(follower_name).and_return(false)
        allow(users_repository).
          to receive(:registered?).with(followed_name).and_return(true)

        expect{Actions::FollowUser.do follower_name, followed_name}.
          to raise_error(Users::Errors::NonRegistered)
      end
    end
  end
end
```

```ruby
require 'spec_helper'
require 'cos/queries/users/followers_of_user'

describe Queries::FollowersOfUser do
  let(:follower_names) {["pepe", "juan"]}
  let(:followed_name) {"koko"}
  let(:users_repository) { double('UsersRepository') }

  before do
    stub_const('Users::Repository', users_repository)
  end

  describe "Getting the followers of a user" do
    it "returns the list of followers" do
      allow(users_repository).to receive(:followers_of)
      .with(followed_name)
      .and_return(follower_names)

      expect(Queries::FollowersOfUser.do(followed_name)).to eq follower_names
    end
  end
end
```

In these tests, every time we allow or expect a method call on our repository double,
we are defining not only *the messages that the users repository can respond to* (its *public interface*)<a href="#nota1"><sup>[1]</sup></a> but also *what its clients can expect from each of those messages*, i.e. *its contract*.

In other words, at the same time we were testing the application services, we *defined from the point of view of its clients the responsibilities that the users repository should be accountable for*.

The users repository is at the boundary of our domain. It's a [*port*](https://alistair.cockburn.us/hexagonal-architecture/) that *allows us to not have to know anything about how* users are stored, found, etc. This way we are able to *just focus on what its clients want it to do for them*, i.e., *its responsibilities*.

Focusing on the responsibilities results in more stable interfaces. As I heard [Sandi Metz](https://sandimetz.com/) say once:

*"You can trade the unpredictability of what others do for the constancy of what you want."*<a href="#nota2"><sup>[2]</sup></a>

which is a very nice way to explain the *"Program to an interface, not an implementation"*<a href="#nota3"><sup>[3]</sup></a> design principle.

*How those responsibilities are carried out* is something that each different implementation (or *adapter*) of the users repository *port* is responsible for. However, the terms of *the contract that its clients rely on, must be respected by all of the adapters*. They must play their *roles*. In this sense, *any adapter must be substitutable by any other without the clients being affected*, (yes, you're right, it's the [Liskov substitution principle](https://en.wikipedia.org/wiki/Liskov_substitution_principle)).

<h2>Role or contract tests.</h2>

The only way to ensure this *substitutability* is by testing each adapter to check if it also *respects the terms of the contract*, i. e. it *fulfils its role*. Those tests would ensure that the *Liskov substitution principle* is respected<a href="#nota4"><sup>[4]</sup></a>.

I will use the term *role test* used by Sandi Metz because *contract test* has become overloaded<a href="#nota5"><sup>[5]</sup></a>.

Ok, but how can we test that all the possible implementations of the user repository respect the contract without repeating a bunch of test code?

<h2>Using shared examples in RSpec to write role tests.</h2>

There’s one very readable way to do it in Ruby using [RSpec](https://rspec.info/).

We created a [RSpec shared example](https://relishapp.com/rspec/rspec-core/v/3-10/docs/example-groups/shared-examples) in a file named *users_repository_role.rb* where we wrote the tests that describes the behaviour that users repository clients were relying on:

```ruby
require 'spec_helper'

RSpec.shared_examples "users repository" do
  let(:user_name) { '@foolano' }
  let(:follower_name) { '@zutano' }
  let(:followed_name) { '@mengano' }

  it "knows when a user is registered" do
    given_already_registered(user_name)

    expect(@repo.registered?(user_name)).to be_truthy
  end

  it "knows when a user is not registered" do
    expect(@repo.registered?(user_name)).to be_falsy
  end

  it "registers a new user" do
    @repo.register(user_name)

    expect(@repo.registered?(user_name)).to be_truthy
  end

  it "adds a follower to a user" do
    given_both_already_registered(follower_name, followed_name)

    @repo.add_follower(follower_name, followed_name)

    expect(@repo.followers_of(followed_name)).to eq [follower_name]
  end

  def given_both_already_registered(follower_name, followed_name)
    given_already_registered(follower_name)
    given_already_registered(followed_name)
  end

  def given_already_registered(user_name)
    @repo.register(user_name)
  end
end
```

Then for each implementation of the users repository you just need to include the role tests using RSpec `it_behaves_like` method, as shown in the following two implementations:

```ruby
require 'spec_helper'
require 'infrastructure/users/repositories/in_memory'
require_relative '../cos/repositories_contracts/users_repository_role'

describe "In memory users repository" do
  before do
    @repo = Users::Repositories::InMemory.new
  end

  it_behaves_like "users repository"
end
```

```ruby
require 'spec_helper'
require 'infrastructure/users/repositories/mongoid'
require_relative '../cos/repositories_contracts/users_repository_role'

describe "Mongoid users repository" do
  before do
    @repo = Users::Repositories::Mongoid.new
  end

  it_behaves_like "users repository"
end
```

You could still add any other test that only has to do with a given implementation in its specific test.

This solution is very readable and reduces a lot of duplication in the tests. However, the idea of *role tests* is not only important from the point of view of avoiding duplication in test code. In dynamic languages, such as Ruby, they also serve as a mean to *highlight and document the role of duck types* that might otherwise go unnoticed because there is no interface construct.

<h2>Acknowledgements.</h2>

I’d like to thank my Codesai colleagues for reading the initial drafts and giving me feedback.

<h2>Notes.</h2>

<a name="nota1"></a> [1] Read more about objects communicating by sending and receiving messages in [Alan Kay's Definition Of Object Oriented](https://wiki.c2.com/?AlanKaysDefinitionOfObjectOriented)

<a name="nota2"></a> [2] You can find a slightly different wording of it in her great talk [Less - The Path to Better Design](https://vimeo.com/26330100)  at 29’48’’.

<a name="nota3"></a> [3] Presented in chapter one of [Design Patterns: Elements of Reusable Object-Oriented Software](https://en.wikipedia.org/wiki/Design_Patterns) book.

<a name="nota4"></a> [4] This is similar to [J. B. Rainsberger](http://www.jbrains.ca/)'s idea of *contract tests* mentioned in his [Integrated Tests Are A Scam talk](https://vimeo.com/80533536) and also to [Jason Gorman](http://codemanship.co.uk/parlezuml/blog/)'s idea of [polymorphic testing](http://codemanship.co.uk/parlezuml/blog/?postid=1183).

<a name="nota5"></a> [5] For example [Martin Fowler](https://en.wikipedia.org/wiki/Martin_Fowler_(software_engineer)) uses contract test to define a different concept in [Contract Test](https://martinfowler.com/bliki/ContractTest.html).

<h2>References.</h2>

* [Practical Object-Oriented Design, An Agile Primer Using Ruby](https://www.poodr.com/), [Sandi Metz](https://sandimetz.com/)
* [Defining Object-Oriented Design](https://www.youtube.com/watch?v=HGT8bKSS6XQ), [Sandi Metz](https://sandimetz.com/)
* [Less - The Path to Better Design](https://vimeo.com/26330100), [Sandi Metz](https://sandimetz.com/)
* [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)
* [Integrated Tests Are A Scam talk](https://vimeo.com/80533536), [J. B. Rainsberger](http://www.jbrains.ca/)
* [101 Uses For Polymorphic Testing (Okay... Three)](http://codemanship.co.uk/parlezuml/blog/?postid=1183), [Jason Gorman](http://codemanship.co.uk/parlezuml/blog/)

Photo from [Anna Rye in Pexels](https://www.pexels.com/es-es/@anna-rye-70977670?utm_content=attributionCopyText&utm_medium=referral&utm_source=pexels)