RSpec supports isolated tests. That makes sense, whenever tests are repeated in different contexts. Modules are classic examples for sure.
Testing shared logic will be described by means of the PORO classes Person and Product:
class Person
end
class Product
end
A premature business logic specification for Person in the person_spec.rb:
require 'spec_helper'
describe Person do
subject { Person.new }
describe "attribute accessors" do
it "should have name"
end
describe "#to_s" do
it "should return name"
context "when name is undefined" do
it "should be blank"
end
end
end
Briefly the Person should receive an attr_accessor :name and a name aliasing method to_s additionally.
Sure, the specifications could be completed and their implementations could be put into Person. The tests would give green lights.
But in the case of having the very same requirements applying to other classes (e.g. Product) likewise, it does not make sense to repeat writing the same specs over and over again. In favour of reusing the specs multiple times, they have to be shareable. The completed tests now are in spec/lib/humanizable_spec.rb:
require 'spec_helper'
shared_examples_for 'Humanizable' do
subject { described_class.new }
describe "attribute accessors" do
it "should have name" do
subject.name = 'Alice'
expect(subject.name).to eq('Alice')
end
end
describe '#to_s' do
it "should be equal to name" do
subject.name = 'Alice'
expect(subject.to_s).to eq('Alice')
end
context "when name is undefined" do
it "should be blank" do
subject.name = nil
expect(subject.to_s).to eq('')
end
end
end
end
Basically there are 2 interesting things going on:
- the tests are released for reuse by shared_examples_for (‘Humanizable’)
- the context for testing (e.g. Person or Product) is defined by described_class
Everything else is merely routine.
The tests just have to be shared. The former person_spec.rb once again, but shorter:
require 'spec_helper'
require 'lib/humanizable_spec'
RSpec.describe Person do
it_behaves_like 'Humanizable'
end
There is a declaration, that the class under test has to behave like a ‘Humanizable’. Since the same behaviour is intended to apply to the product as well, the product_spec.rb also can benefit from the shared specs:
require 'spec_helper'
require 'lib/humanizable_spec'
RSpec.describe Product do
it_behaves_like 'Humanizable'
end
Running the test suite:
$ rspec spec/person.rb
results in failures, as expected, because the implementation is still missing. Therefore the module lib/humanizable.rb has to be like:
module Humanizable
attr_accessor :name
def to_s
name.to_s
end
end
and finally including it in the classes:
class Person < ActiveRecord::Base
include Humanizable
end
class Product < ActiveRecord::Base
include Humanizable
end
makes running the test suite:
$ rspec
return the green lights:
Finished in 0.00394 seconds (files took 1.43 seconds to load)
6 examples, 0 failures