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.