Bogdan Gusiev's blog

How to make good software for people


Objects behaviour inheritance with RSpec
27 Oct 2009

About half of a year ago I was writing about object interface and Liskov Substitution Princeple. In short: Any class instance that extends the base class should pass all unit tests behaviour tests written for base class instance. It was a surprise for me that this concept has already been implemented in RSpec.

My previous article was primary inspired by Java programming language and it's interface concept. Unlike Java, Ruby does not have interfaces, but behaviour inheritance is still actual for both languages. RSpec seems the first testing framework that provide the ability to validate LSP and behavior inheritance with it_should_behave_like.
With Ruby modules(mixins) feature we can build reusable code and include it in different classes(read more). With RSpec we can bundle the tests as well.

Let's review the following module that uses one of the Rails callback and adds some logging:

module LoggedModel
  def after_save
    super
    handle_logging
  end
end
and the some tests group for this module:

describe LoggableModel
  it "should be loggable" do
    LoggableModel.should ...
  end
end
Now, we have a tested code that is going to be used in many cases like this:
class MyModel
  include LoggableModel
  def after_save
    do_some_other_thing
  end
end
OK, let's see what we have: after_save in MyModel overwrites after_save in LoggableModel and breaks the logging. This is simplest example when the behavior inheritance may be broken. Rspec shared examples groups allows you to ensure that the code in LoggableModel is used correctly from any inherited class. Let's change the definithin of LoggableModel tests.

shared_examples_for "logged model" do
  it "should be loggable" do
    subject.should...
  end
end

'Subject' is the ultimate RSpec magic that let us make a simple abstraction with the tested class and reuse these shared examples in MyModel spec:
describe MyModel do
  it_should_behave_like 'loggable model'
end
In this way we will rerun the LoggableModel examples for MyModel and make sure that it's behavior wasn't broken.

test ruby rspec behaviour inheritance