(`describe`/`it`)
RSpec is a DSL for creating executable examples of how code is expected to
behave, organized in groups. It uses the words “describe” and “it” so we can
express concepts like a conversation:
"Describe an account when it is first opened."
"It has a balance of zero."
The describe
method creates an example group. Within the block passed todescribe
you can declare nested groups using the describe
or context
methods, or you can declare examples using the it
or specify
methods.
Under the hood, an example group is a class in which the block passed todescribe
or context
is evaluated. The blocks passed to it
are evaluated
in the context of an instance of that class.Scenarios
One group, one example
Givena file named “sample_spec.rb” with:
RSpec.describe "something" do
it "does something" do
end
end
WhenI run rspec sample_spec.rb -fdoc
Thenthe output should contain:
something
does something
Nested example groups (using `context`)
Givena file named “nested_example_groups_spec.rb” with:
RSpec.describe "something" do
context "in one context" do
it "does one thing" do
end
end
context "in another context" do
it "does another thing" do
end
end
end
WhenI run rspec nested_example_groups_spec.rb -fdoc
Thenthe output should contain:
something
in one context
does one thing
in another context
does another thing
Shared examples
Shared examples let you describe behaviour of classes or modules. When declared,
a shared group’s content is stored. It is only realized in the context of
another example group, which provides any context the shared group needs to
run.
A shared group is included in another group using any of:
include_examples "name" # include the examples in the current context
it_behaves_like "name" # include the examples in a nested context
it_should_behave_like "name" # include the examples in a nested context
matching metadata # include the examples in the current context
WARNING: Files containing shared groups must be loaded before the files that
use them. While there are conventions to handle this, RSpec does not do
anything special (like autoload). Doing so would require a strict naming
convention for files that would break existing suites.
WARNING: When you include parameterized examples in the current context multiple
times, you may override previous method definitions and last declaration wins.
So if you have this kind of shared example (or shared context)
RSpec.shared_examples "some example" do |parameter|
\# Same behavior is triggered also with either `def something; 'some value'; end`
\# or `define_method(:something) { 'some value' }`
let(:something) { parameter }
it "uses the given parameter" do
expect(something).to eq(parameter)
end
end
RSpec.describe SomeClass do
include_examples "some example", "parameter1"
include_examples "some example", "parameter2"
end
You’re actually doing this (notice that first example will fail):
RSpec.describe SomeClass do
\# Reordered code for better understanding of what is happening
let(:something) { "parameter1" }
let(:something) { "parameter2" }
it "uses the given parameter" do
\# This example will fail because last let "wins"
expect(something).to eq("parameter1")
end
it "uses the given parameter" do
expect(something).to eq("parameter2")
end
end
To prevent this kind of subtle error a warning is emitted if you declare multiple
methods with the same name in the same context. Should you get this warning
the simplest solution is to replace include_examples
with it_behaves_like
, in this
way method overriding is avoided because of the nested context created by it_behaves_like
Conventions:
- The simplest approach is to require files with shared examples explicitly
from the files that use them. Keep in mind that RSpec adds thespec
directory to theLOAD_PATH
, so you can sayrequire
to require a file at
'shared_examples_for_widgets'#{PROJECT_ROOT}/spec/shared_examples_for_widgets.rb
. - One convention is to put files containing shared examples in
spec/support/
and require files in that directory fromspec/spec_helper.rb
:Dir["./spec/support/**/*.rb"].sort.each { |f| require f }
Historically, this was included in the generatedspec/spec_helper.rb
file inrspec-rails
. However, in order to keep your test suite boot time down,
it’s a good idea to not autorequire all files in a directory like this.
When running only one spec file, loading unneeded dependencies or performing
unneeded setup can have a significant, noticable effect on how long it takes
before the first example runs. - When all of the groups that include the shared group reside in the same file,
just declare the shared group in that file.
Shared examples group included in two groups in one file
Givena file named “collection_spec.rb” with:
require "set"
RSpec.shared_examples "a collection" do
let(:collection) { described_class.new([7, 2, 4]) }
context "initialized with 3 items" do
it "says it has three items" do
expect(collection.size).to eq(3)
end
end
describe "#include?" do
context "with an item that is in the collection" do
it "returns true" do
expect(collection.include?(7)).to be(true)
end
end
context "with an item that is not in the collection" do
it "returns false" do
expect(collection.include?(9)).to be(false)
end
end
end
end
RSpec.describe Array do
it_behaves_like "a collection"
end
RSpec.describe Set do
it_behaves_like "a collection"
end
WhenI run rspec collection_spec.rb --format documentation
Thenthe examples should all passAndthe output should contain:
Array
behaves like a collection
initialized with 3 items
says it has three items
#include?
with an item that is in the collection
returns true
with an item that is not in the collection
returns false
Set
behaves like a collection
initialized with 3 items
says it has three items
#include?
with an item that is in the collection
returns true
with an item that is not in the collection
returns false
Providing context to a shared group using a block
Givena file named “shared_example_group_spec.rb” with:
require "set"
RSpec.shared_examples "a collection object" do
describe "<<" do
it "adds objects to the end of the collection" do
collection << 1
collection << 2
expect(collection.to_a).to match_array([1, 2])
end
end
end
RSpec.describe Array do
it_behaves_like "a collection object" do
let(:collection) { Array.new }
end
end
RSpec.describe Set do
it_behaves_like "a collection object" do
let(:collection) { Set.new }
end
end
WhenI run rspec shared_example_group_spec.rb --format documentation
Thenthe examples should all passAndthe output should contain:
Array
behaves like a collection object
<<
adds objects to the end of the collection
Set
behaves like a collection object
<<
adds objects to the end of the collection
Passing parameters to a shared example group
Givena file named “shared_example_group_params_spec.rb” with:
RSpec.shared_examples "a measurable object" do |measurement, measurement_methods|
measurement_methods.each do |measurement_method|
it "should return #{measurement} from ##{measurement_method}" do
expect(subject.send(measurement_method)).to eq(measurement)
end
end
end
RSpec.describe Array, "with 3 items" do
subject { [1, 2, 3] }
it_should_behave_like "a measurable object", 3, [:size, :length]
end
RSpec.describe String, "of 6 characters" do
subject { "FooBar" }
it_should_behave_like "a measurable object", 6, [:size, :length]
end
WhenI run rspec shared_example_group_params_spec.rb --format documentation
Thenthe examples should all passAndthe output should contain:
Array with 3 items
it should behave like a measurable object
should return 3 from #size
should return 3 from #length
String of 6 characters
it should behave like a measurable object
should return 6 from #size
should return 6 from #length
Aliasing `it_should_behave_like` to `it_has_behavior`
Givena file named “shared_example_group_spec.rb” with:
RSpec.configure do |c|
c.alias_it_should_behave_like_to :it_has_behavior, 'has behavior:'
end
RSpec.shared_examples 'sortability' do
it 'responds to <=>' do
expect(sortable).to respond_to(:<=>)
end
end
RSpec.describe String do
it_has_behavior 'sortability' do
let(:sortable) { 'sample string' }
end
end
WhenI run rspec shared_example_group_spec.rb --format documentation
Thenthe examples should all passAndthe output should contain:
String
has behavior: sortability
responds to <=>
Sharing metadata automatically includes shared example groups
Givena file named “shared_example_metadata_spec.rb” with:
RSpec.shared_examples "shared stuff", :a => :b do
it 'runs wherever the metadata is shared' do
end
end
RSpec.describe String, :a => :b do
end
Shared examples are nestable by context
Consider the following spec
context_specific_examples_spec.rb
RSpec.describe "shared examples" do
context "per context" do
shared_examples "shared examples are nestable" do
specify { expect(true).to eq true }
end
it_behaves_like "shared examples are nestable"
end
end
Shared examples are accessible from offspring contexts
In this example, we show that it_behaves_like can be nested with a context
RSpec.describe "shared examples" do
shared_examples "shared examples are nestable" do
specify { expect(true).to eq true }
end
context "per context" do
it_behaves_like "shared examples are nestable"
end
end
Shared examples are isolated per context
In this example, we show that shared examples are isolated to only the context they are declared within.
RSpec.describe "shared examples" do
context do
shared_examples "shared examples are isolated" do
specify { expect(true).to eq true }
end
end
context do
it_behaves_like "shared examples are isolated"
end
end
In this case, we get an error:
Could not find shared examples \"shared examples are isolated\"
Shared context
Use shared_context
to define a block that will be evaluated in the context of example groups either locally, using include_context
in an example group, or globally using config.include_context
.
When implicitly including shared contexts via matching metadata, the normal way is to define matching metadata on an example group, in which case the context is included in the entire group. However, you also have the option to include it in an individual example instead. RSpec treats every example as having a singleton example group (analogous to Ruby’s singleton classes) containing just the one example.
Consider the following Rspec example configuration
RSpec.configure do |rspec|
# This config option will be enabled by default on RSpec 4,
# but for reasons of backwards compatibility, you have to
# set it on RSpec 3.
#
# It causes the host group and examples to inherit metadata
# from the shared context.
rspec.shared_context_metadata_behavior = :apply_to_host_groups
end
RSpec.shared_context "shared stuff", :shared_context => :metadata do
before { @some_var = :some_value }
def shared_method
"it works"
end
let(:shared_let) { {'arbitrary' => 'object'} }
subject do
'this is the subject'
end
end
RSpec.configure do |rspec|
rspec.include_context "shared stuff", :include_shared => true
end
Declare a shared context and include it with `include_context`
Consider the following shared_context_example.rb
require "./shared_stuff.rb"
RSpec.describe "group that includes a shared context using 'include_context'" do
include_context "shared stuff"
it "has access to methods defined in shared context" do
expect(shared_method).to eq("it works")
end
it "has access to methods defined with let in shared context" do
expect(shared_let['arbitrary']).to eq('object')
end
it "runs the before hooks defined in the shared context" do
expect(@some_var).to be(:some_value)
end
it "accesses the subject defined in the shared context" do
expect(subject).to eq('this is the subject')
end
group = self
it "inherits metadata from the included context" do |ex|
expect(group.metadata).to include(:shared_context => :metadata)
expect(ex.metadata).to include(:shared_context => :metadata)
end
end
Declare a shared context, include it with `include_context` and extend it with an additional block
Givena file named “shared_context_example.rb” with:
require "./shared_stuff.rb"
RSpec.describe "including shared context using 'include_context' and a block" do
include_context "shared stuff" do
let(:shared_let) { {'in_a' => 'block'} }
end
it "evaluates the block in the shared context" do
expect(shared_let['in_a']).to eq('block')
end
end
rspec shared_context_example.rb
Declare a shared context and include it with metadata
Givena file named “shared_context_example.rb” with:
require "./shared_stuff.rb"
RSpec.describe "group that includes a shared context using metadata", :include_shared => true do
it "has access to methods defined in shared context" do
expect(shared_method).to eq("it works")
end
it "has access to methods defined with let in shared context" do
expect(shared_let['arbitrary']).to eq('object')
end
it "runs the before hooks defined in the shared context" do
expect(@some_var).to be(:some_value)
end
it "accesses the subject defined in the shared context" do
expect(subject).to eq('this is the subject')
end
group = self
it "inherits metadata from the included context" do |ex|
expect(group.metadata).to include(:shared_context => :metadata)
expect(ex.metadata).to include(:shared_context => :metadata)
end
end
WhenI run rspec shared_context_example.rb
Thenthe examples should all pass
Declare a shared context and include it with metadata of an individual example
Givena file named “shared_context_example.rb” with:
require "./shared_stuff.rb"
RSpec.describe "group that does not include the shared context" do
it "does not have access to shared methods normally" do
expect(self).not_to respond_to(:shared_method)
end
it "has access to shared methods from examples with matching metadata", :include_shared => true do
expect(shared_method).to eq("it works")
end
it "inherits metadata from the included context due to the matching metadata", :include_shared => true do |ex|
expect(ex.metadata).to include(:shared_context => :metadata)
end
end
WhenI run rspec shared_context_example.rb
Thenthe examples should all pass
Aliasing
describe
and context
are the default aliases for example_group
. You can define your own aliases for example_group
and give those custom aliases default metadata.
RSpec provides a few built-in aliases:
xdescribe
andxcontext
add:skip
metadata to the example group in order to temporarily disable the examples.fdescribe
andfcontext
add:focus
metadata to the example group in order to make it easy to temporarily focus the example group (when combined withconfig.filter_run :focus
.)
Scenarios
Custom example group aliases with metadata
Givena file named “nested_example_group_aliases_spec.rb” with:
RSpec.configure do |c|
c.alias_example_group_to :detail, :detailed => true
end
RSpec.detail "a detail" do
it "can do some less important stuff" do
end
end
RSpec.describe "a thing" do
describe "in broad strokes" do
it "can do things" do
end
end
detail "something less important" do
it "can do an unimportant thing" do
end
end
end
WhenI run rspec nested_example_group_aliases_spec.rb --tag detailed -fdoc
Thenthe output should contain:
a detail
can do some less important stuff
a thing
something less important