contract tests in rspec

Here’s a quick experience report, about something I noticed myself doing this week…

I’m developing a ruby app that’s something like a library. I have some code that collects the names of the user’s 5 last visited items, which could be books, DVDs, MP3 downloads etc. Right now we only need the items’ names, so the collector calls the <code>name</code> method on each object in the collection, and duck-typing does the rest, right?

Well, only if each class of “visitable” thing defines a <code>name</code> method that has the right semantics for this collection. if this were a statically typed language such as Java we’d define a <code>Visitable</code> interface and have classes Book, DVD, MP3 implement it. But this is Ruby, so we don’t do that. And yet, when I add another “visitable” class, I need to get those semantics right or else I’ll break that collection (or worse, something far away that uses it).

This is where J.B.Rainsberger’s idea of contract tests helps out. If I define a set of tests for the contract between the collection and the “visitables”, I’ll get some advance warning of likely trouble when I add more visitable classes.

In this case, all we need is for the “visitable” object to have a non-nil name. I write that contract as a shared example group:

shared_examples_for 'a visitable item' do
  its(:name) { should_not be_nil }

And then check that every “visitable” class implements it correctly:

require 'spec_helper'

describe Book do
  it_should_behave_like 'a visitable item'

  # more specs ...

And as if to prove the point, when I added this test to the specs for every “visitable” class, one of them failed. I had a broken collection waiting just around the corner, but the contract tests made sure no-one saw it before it was fixed.

In general, contract tests will be richer than this example. But I think the approach holds good: use a shared example group to “specify” the contract between a client and a polymorphic group of supplier classes.


Ashley Moran suggests aliasing the standard rspec methods to make it clearer that these are contract tests. And after a wee bit of discussion we settled on this:

contract_for 'a visitable item' do
  its(:name) { should_not be_nil }

and this:

describe Book do
  it_satisfies_contract_for 'a visitable item'

  # more specs ...

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s