Library providing tools for writing and running RSpec tests.
Defines helpers for including reusable contracts in RSpec example groups.
RSpec contracts are a mechanism for sharing tests between projects. For example, one library may define an interface or specification for a type of object, while a second library implements that object. By defining a contract and sharing that contract as part of the library, the developer ensures that any object that matches the contract has correctly implemented and conforms to the interface. This reduces duplication of tests and provides resiliency as an interface is developed over time and across versions of the library.
Mechanically speaking, each contract encapsulates a section of RSpec code. When the contract is included in a spec, that code is then injected into the spec. Writing a contract, therefore, is no different than writing any other RSpec specification - it is only the delivery mechanism that differs. A contract can be any object that responds to #to_proc; the simplest contract is therefore a Proc or lambda that contains some RSpec code.
Defining A Contract
module ExampleContracts
# This contract asserts that the object has the Enumerable module as an
# ancestor, and that it responds to the #each method.
SHOULD_BE_ENUMERABLE_CONTRACT = lambda do
it 'should be Enumerable' do
expect(subject).to be_a Enumerable
end
it 'should respond to #each' do
expect(subject).to respond_to(:each).with(0).arguments
end
end
end
RSpec.describe Array do
extend RSpec::SleepingKingStudios::Concerns::IncludeContract
include_contract ExampleContracts::SHOULD_BE_ENUMERABLE_CONTRACT
end
RSpec.describe Hash do
extend RSpec::SleepingKingStudios::Concerns::IncludeContract
include ExampleContracts
include_contract 'should be enumerable'
end
Defining A Contract With Parameters
module SerializerContracts
# This contract asserts that the serialized result has the expected
# values.
#
# First, we pass the contract a series of attribute names. These are
# used to assert that the serialized attributes match the values on the
# original object.
#
# Second, we pass the contract a set of attribute names and values.
# These are used to assert that the serialized attributes have the
# specified values.
#
# Finally, we can pass the contract a block, which the contract then
# executes. Note that the block is executed in the context of our
# describe block, and thus can take advantage of our memoized
# #serialized helper method.
SHOULD_SERIALIZE_ATTRIBUTES_CONTRACT = lambda \
do |*attributes, **values, &block|
describe '#serialize' do
let(:serialized) { subject.serialize }
it { expect(subject).to respond_to(:serialize).with(0).arguments }
attributes.each do |attribute|
it "should serialize #{attribute}" do
expect(serialized[attribute]).to be == subject[attribute]
end
end
values.each do |attribute, value|
it "should serialize #{attribute}" do
expect(serialized[attribute]).to be == value
end
end
instance_exec(&block) if block
end
end
RSpec.describe CaptainPicard do
extend RSpec::SleepingKingStudios::Concerns::IncludeContract
include SerializerContracts
include_contract 'should serialize attributes',
:name,
:rank,
lights: 4 do
it 'should serialize the catchphrase' do
expect(serialized[:catchphrase]).to be == 'Make it so.'
end
end
end
#include_contract(contract, *arguments, **keywords, &block) => Object
#include_contract(contract_name, *arguments, **keywords, &block) => Object
Adds the contract to the example group with the given parameters.
#include_contract(contract, *arguments, **keywords, &block) => Object
#include_contract(contract_name, *arguments, **keywords, &block) => Object
Back to Documentation | Versions | 2.8.1 | Reference | RSpec | RSpec::SleepingKingStudios | RSpec::SleepingKingStudios::Concerns