The best part of choosing Rspec as your tool for automated testing, is the possibility to describe your expectations with very readable sentences. Today, We will present to you two components of Rspec that can make it even more doable.
Shared Contexts
Shared contexts is a way to stick to the don’t repeat yourself rule. It allows us to reuse some test boilerplate such as let, subject, described_class, and other generalisms, for different sets of tests. Let’s see the example below:
# app/models/bottle.rb
class Bottle < ApplicationRecord
MAX_CAPACITY = 330
def full?
current_content >= MAX_CAPACITY
end
end
# app/models/bowl.rb
class Bowl < ApplicationRecord
MAX_CAPACITY = 600
def full?
current_content >= MAX_CAPACITY
end
End
Let’s imagine two models, Bottle and Bowl. As containers, they share a common method called full?. Let’s see how we could write our tests using shared_context:
# spec/support/shared_contexts/filled.rb
RSpec.shared_context 'filled' do
let(:current_capacity) { 50 }
subject { described_class.new(current_capacity: current_capacity) }
end
# spec/models/bottle_spec.rb
RSpec.describe Bottle, type: :model do
describe 'full?' do
context 'when the bottle is' do
context 'not full' do
include_context 'filled'
it 'returns false' do
expect(subject).to be_falsey
end
end
context 'is full' do
include_context 'filled' do
let(:current_capacity) { 330 }
end
it 'returns false' do
expect(subject).to be_falsey
end
end
end
end
end
# spec/models/bowl_spec.rb
RSpec.describe Bowl, type: :model do
describe 'filled?' do
context 'when the bowl is' do
context 'not full' do
include_context 'filled'
it 'returns false' do
expect(subject).to be_falsey
end
end
context 'is full' do
include_context 'filled' do
let(:current_capacity) { 600 }
<pre><code class="language-ruby"> end</code></pre>
it 'returns true' do
expect(subject).to be_truthy
end
end
end
end
End
See? The tests are using the same configuration context and build different objects to run correctly. Easy and clean, no? Perfect but, reading the specs you could say: Ok, but the tests per si seem pretty equal to me and duplicated no? I need to agree with you. The next topic will help us to improve that.
Shared Examples
Shared examples allow us to reuse a bunch of tests for multiple features of our applications. Using the same example above, we could have something like this:
# spec/support/shared_examples/fillable_spec.rb
RSpec.shared_examples 'Fillable' do |received_capacity|
describe 'full?' do
context "when the #{described_class.downcase}" is do
context 'not full' do
include_context 'filled'
it 'returns false' do
expect(subject).to be_falsey
end
end
context 'is full' do
include_context 'filled' do
let(:current_capacity) { received_capacity }
end
it 'returns true' do
expect(subject).to be_truthy
end
end
end
end
end
# spec/models/bottle_spec.rb
RSpec.describe Bottle, type: :model do
it_behaves_like 'Fillable', 330
end
# spec/models/bowl_spec.rb
RSpec.describe Bowl, type: :model do
it_behaves_like 'Fillable', 600
end
Clean and tidy code so when you look at it a year from now you’ll be proud. Shared context and examples are great tools to DRY up your code, make it more readable and pleasure to work with it. As with any examples, it’s best practice to be cautious about the complexity of the abstractions when using these tools. If you need to override variables of the context all the time, or over-configure the call to the shared examples, it could indicate you are in search of a different abstraction solution.