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
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:
and the some tests group for this module:
module LoggedModel def after_save super handle_logging end end
Now, we have a tested code that is going to be used in many cases like this:
describe LoggableModel it "should be loggable" do LoggableModel.should ... 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.
class MyModel include LoggableModel def after_save do_some_other_thing 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:
shared_examples_for "logged model" do it "should be loggable" do subject.should... end end
In this way we will rerun the LoggableModel examples for MyModel and make sure that it's behavior wasn't broken.
describe MyModel do it_should_behave_like 'loggable model' end