RSpec is a Behaviour-Driven Development tool for Ruby programmers.
BDD is an approach to software development that combines Test-Driven Development, Domain Driven Design, and Acceptance Test-Driven Planning.
RSpec helps you do the TDD part of that equation, focusing on the documentation and design aspects of TDD.
Basic Structure
"Describe an order." "It sums the prices of its line items."
RSpec.describe Order do it "sums the prices of its items" do order = Order.new order.add_item(price: Money.new(1.11, :USD) ) order.add_item(price: Money.new(2.22, :USD), quality: 2 ) expect(order.total).to eq(Money.new(5.55, :USD)) end end
Nested Groups
describe
or context
都可以RSpec.describe Order do context "with no items" do it "behaves one way" {} end context "with one item" do it "behaves another way" {} end end
Shared Examples and Contexts
shared_examples
include_examples
RSpec.shared_examples "collections" do |collection_class| it "is empty when first created" do expect(collection_class.new).to be_empty end end RSpec.describe Array do include_examples "collections", Array end RSpec.describe Hash do include_examples "collections", Hash end
Nearly anything that can be declared within an example group can be declared within a shared example group. This includes
before
, after
, and around
hooks, let
declarations, and nested groups/contexts.You can also use the names
shared_context
and include_context
. These are pretty much the same as shared_examples
and include_examples
, providing more accurate naming when you share hooks, let
declarations, helper methods, etc, but no examples.Subject
Implicitly defined subject
If the first argument to an example group is a class, an instance of that
class is exposed to each example in that example group via the subject method
RSpec.describe Array do it "should be empty when first created" do expect(subject).to be_empty end describe "when first created" do # `subject` in a nested group it "should be empty" { expect(subject).to be_empty } end end
method.
Explicit Subject
RSpec.describe Array, "with some elements" do subject { [1, 2, 3] } it "has the prescribed elements" do expect(subject).to eq([1, 2, 3]) end end
One-liner syntax
RSpec.describe Array do describe "with 3 items" do subject { [1,2,3] } it { should_not be_empty } it { is_expected.not_to be_empty } end end
Hooks
Use
before
and after
hooks to execute arbitrary code before and/or afterthe body of an example is run:before(:example) # run before each example before(:context) # run one time only, before all of the examples in a group after(:example) # run after each example after(:context) # run one time only, after all of the examples in a group
Aliases
You can declare example groups using either
describe
or context
. For a top level example group, describe
and context
are available off of RSpec
. For backwards compatibility, they are also available off of the main
object and Module
unless you disable monkey patching.You can declare examples within a group using any of
it
, specify
, or example
.Metadata
rspec-core stores a metadata hash with every example and group, which contains their descriptions, the locations at which they were declared, etc, etc. This hash powers many of rspec-core's features, including output formatters (which access descriptions and locations), and filtering before and after hooks.
Although you probably won't ever need this unless you are writing an extension, you can access it from an example like this:
it "does something" do |example| expect(example.metadata[:description]).to eq("does something") end
described_class
When a class is passed to
describe
, you can access it from an example using the described_class
method, which is a wrapper for example.metadata[:described_class]
.RSpec.describe Widget do example do expect(described_class).to equal(Widget) end end
This is useful in extensions or shared example groups in which the specific class is unknown. Taking the collections shared example group from above, we can clean it up a bit using
described_class
:RSpec.shared_examples "collections" do it "is empty when first created" do expect(described_class.new).to be_empty end end RSpec.describe Array do include_examples "collections"end RSpec.describe Hash do include_examples "collections"end
Let
Use
let
to define a memoized helper method. The value will be cached acrossmultiple calls in the same example but not across examples.Note that
let
is lazy-evaluated: it is not evaluated until the first timethe method it defines is invoked. You can use let!
to force the method'sinvocation before each example.By default,
let
is threadsafe, but you can configure it not to be$count = 0 RSpec.describe "let" do let(:count) { $count += 1 } it "memoizes the value" do expect(count).to eq(1) expect(count).to eq(1) end it "is not cached across examples" do expect(count).to eq(2) end end