Baike.dev
All toolsOnline toolsAI codingTrendingOpen sourceNewsSubmit
Log in
< Back to tools
R

rspec-given

> 编程语言
Open source

Given/When/Then keywords for RSpec Specifications

652 stars0 likes2 views
WebsiteGitHub

About

Given/When/Then keywords for RSpec Specifications

Given/When/Then for RSpec and Minitest

Master

Covering rspec-given, minitest-given, and given-core, version 3.5.3.

rspec-given and minitest-given are extensions to your favorite testing framework to allow Given/When/Then notation when writing specs.

Why Given/When/Then

RSpec has done a great job of making specifications more readable for humans. However, I really like the given/when/then nature of Cucumber stories and would like to follow the same structure in my unit tests. rspec-given (and now minitest-given) allows a simple given/when/then structure RSpec specifications.

Status

  • rspec-given and minitest-given are ready for production use.

RSpec/Given

The rspec-given gem is the original given/when/then extension for RSpec. It now depends on a given_core gem for the basic functionality and then adds the RSpec specific code.

  • rspec-given now requires RSpec version 2.12 or better.

Minitest/Given

A new minitest-given gem allows Given/When/Then notation directly in Minitest::Spec specifications.

To use minitest-given, just place the following require at the top of the file (or in a convenient spec_helper).

ruby
require 'minitest/given'

All the features of rspec-given are available in minitest-given.

When switching from RSpec/Given to Minitest/Given, here are some things to watch out for:

  • You need to use Minitest version 4.3 or better (yes, Minitest 5.x should work as well).

  • Minitest/Given adds the missing "context" block to Minitest::Spec.

  • Only one before block is allowed in any given Minitest::Spec describe block. This doesn't effect the number of Givens you are allowed to use, but it may surprise if you are use to RSpec.

Auto Selecting

If you use natural assertions exclusively in your specs, it's quite possible to write specs that run under both RSpec and Minitest::Spec.

Use this at the start of your spec file:

ruby
if defined?(RSpec)
  require 'rspec/given'
else
  require 'minitest/autorun'
  require 'minitest/given'
end

See stack_spec.rb and example_helper.rb

Installation

If you are using bundler

Add rspec-given (or minitest-given) to the :test group in the Gemfile:

ruby
group :test do
  gem 'rspec-given'
end
ruby
group :test do
  gem 'minitest-given'
end

Download and install:

$ bundle

Then just require rspec/given (or minitest/given) in the spec_helper of your project and it is ready to go.

If you are not using bundler

Install the gem:

$ gem install rspec-given

or

$ gem install minitest-given

Then just require rspec/given (or minitest/given) in the spec_helper of your project and it is ready to go.

Example

Here is a specification written in the rspec-given framework:

…

Let's talk about the individual statements used in the Given framework.

Given

The Given section specifies a starting point, a set of preconditions that must be true before the code under test is allowed to be run. In standard test frameworks the preconditions are established with a combination of setup methods (or :before actions in RSpec) and code in the test.

In the example code above the preconditions are started with Given statements. A top level Given (that applies to the entire describe block) says that one of the preconditions is that there is a stack with some initial contents.

Note that initial contents are not specified in the top level describe block, but are given in each of the nested contexts. By pushing the definition of "initial_contents" into the nested contexts, we can vary them as needed for that particular context.

A precondition in the form "Given(:var) {...}" creates an accessor method named "var". The accessor is lazily initialized by the code block. If you want a non-lazy given, use "Given!(:var) {...}".

A precondition in the form "Given {...}" just executes the code block for side effects. Since there is no accessor, the code block is executed immediately (i.e. no lazy evaluation).

The preconditions are run in order of definition. Nested contexts will inherit the preconditions from the enclosing context, with outer preconditions running before inner preconditions.

Given examples:

ruby
    Given(:stack) { Stack.new }

The block for the given clause is lazily run and its value bound to 'stack' if 'stack' is ever referenced in the test. The first reference to 'stack' in the specification will cause the code block to execute. Futher references to 'stack' will reuse the previously generated value.

ruby
    Given!(:original_size) { stack.size }

The code block is run unconditionally once before each test and the value of the block is bound to 'original_size'. This form is useful when you want to record the value of something that might be affected by the When code.

ruby
    Given { stack.clear }

The block for the given clause is run unconditionally once before each test. This form of given is used for code that is executed for side effects.

When

The When clause specifies the code to be tested ... oops, excuse me ... specified. After the preconditions in the given section are met, the when code block is run.

In general there should not be more than one When clause for a given direct context. However, a When in an outer context will be run after all the Givens but before the inner When. You can think of an outer When as setting up additional given state for the inner When.

E.g.

ruby
    context "outer context" do
      When { code specified in the outer context }
      Then { assert something about the outer context }

      context "inner context" do

        # At this point, the _When_ of the outer context will be run
        # before the _When_ of then inner context (but after all the
        # _Givens_ of all the contexts).  You can think of the outer
        # _When_ as a special given for the inner scope.

        When { code specified in the inner context }
        Then { assert something about the inner context }
      end
    end

When examples:

ruby
    When { stack.push(:item) }

The code block is executed once per test. The effect of the When{} block is very similar to Given{}. However, When is used to identify the particular code that is being specified in the current context or describe block.

ruby
    When(:result) { stack.pop }

The code block is executed once per test and the value of the code block is bound to 'result'. Use this form when the code under test returns a value that you wish to interrogate in the Then code.

If an exception occurs during the execution of the block for the When clause, the exception is caught and a failure object is bound to 'result'. The failure can be checked in a then block with the 'have_failed' matcher.

The failure object will rethrow the captured exception if anything other than have_failed matcher is used on the failure object.

For example, if the stack is empty when it is popped, then it is reasonable for pop to raise an UnderflowError. This is how you might specify that behavior:

ruby
    When(:result) { stack.pop }
    Then { expect(result).to have_failed(UnderflowError, /empty/) }

The arguments to the 'have_failed' matcher are the same as those given to the standard RSpec matcher 'raise_error'.

NOTE: Prior to RSpec 3, the .should method worked with the failed result. In RSpec 3 the .should method is deprecated and should not be used with the failed result.

Then

The Then clauses are the postconditions of the specification. These then conditions must be true after the code under test (the When clause) is run.

The code in the block of a Then clause should be a single should assertion. Code in Then clauses should not have any side effects.

Let me repeat that: Then clauses should not have any side effects! Then clauses with side effects are erroneous. Then clauses need to be idempotent, so that running them once, twice, a hundred times, or never does not change the state of the program. (The same is true of And and Invariant clauses).

In RSpec terms, a Then clause forms a RSpec Example that runs in the context of an Example Group (defined by a describe or context clause).

Each Example Group must have at least one Then clause, otherwise there will be no examples to be run for that group. If all the assertions in an example group are done via Invariants, then the group should use an empty Then clause, like this:

ruby
    Then { }

Then examples:

ruby
    Then { stack.empty? }

After the related block for the When clause is run, the stack should be empty. If it is not empty, the test will fail.

And

The And clause is similar to Then, but does not form its own RSpec example. This means that And clauses reuse the setup from a sibling Then clause. Using a single Then and multiple And clauses in an example group means the setup for that group is run only once (for the Then clause) and reused for all the And clauses. This can be a significant speed savings where the setup for an example group is expensive.

Some things to keep in mind about And clauses:

  • There must be at least one Then in the example group and it must be declared before the And clauses. Forgetting the Then clause is an error.

  • The code in the And clause is run immediately after the first (executed) Then of an example group.

  • An assertion failure in a Then clause or an And clause will cause all the subsequent And clauses to be skipped.

  • Since And clauses do not form their own RSpec examples, they are not represented in the formatted output of RSpec. That means And clauses do not produce dots in the Progress format, nor do they appear in the documentation, html or textmate formats (options -fhtml, -fdoc, or -ftextmate).

  • Like Then clauses, And clauses must be idempotent. That means they should not execute any code that changes global program state. (See the section on the Then clause).

The choice to use an And clause is primarily a speed consideration. If an example group has expensive setup and there are a lot of Then clauses, then choosing to make some of the Then clauses into And clauses will speed up the spec. Otherwise it is probably better to stick with Then clauses.

Then/And examples:

ruby
  Then { pop_result == :top_item }           # Required
  And  { stack.top == :second_item }         # No Setup rerun
  And  { stack.depth == original_depth - 1 } # ... for these

Invariant

The Invariant clause is a new idea that doesn't have an analog in RSpec or Test::Unit. The invariant allows you specify things that must always be true in the scope of the invariant. In the stack example, the method empty? is defined in term of size.

ruby
  Invariant { stack.empty? == (stack.depth == 0) }

This invariant states that empty? is true if and only if the stack depth is zero, and that assertion is checked at every Then clause that is in the same scope.

You can conceptually think of an Invariant clause as a Then block that automatically gets added to every Then within its scope. Invariants nested within a context only apply to the Then clauses that are in the scope of that context.

Invariants that reference a Given precondition accessor must only be used in contexts that define that accessor.

Notes:

  • Since Invariants do not form their own RSpec example, they are not represented in the RSpec formatted output (e.g. the '--format html' option).

Execution Ordering

When running the test for a specific Then clause, the following will be true:

  • The non-lazy Given clauses will

Issues· 0 open

View all issuesOpen on GitHub

No open issues yet, or sync has not completed.

> Tags

Ruby

No comments yet. Be the first to share.

> Details

PublishedAug 1, 2026
UpdatedSep 17, 2026
Category编程语言
PricingOpen source

> Related tools

T
TypeScript
JavaScript 的超集,为前端与全栈提供静态类型
P
Python
通用编程语言,广泛用于 Web、数据与 AI
G
Go
Google 推出的简洁高效系统语言