Helper Methods

let and let!

In RSpec, let and let! are helper methods that allow you to define variables or objects that can be reused in multiple test cases. They differ in when they are evaluated and whether they memoize their values.

let is a lazy evaluation method that defines a memoized helper method. It defines a variable or object that is evaluated only when it is first called in a test case. Once evaluated, the value is cached and reused in subsequent calls within the same test case.

For example, let’s say you have a test case that requires an instance of a Person class. You could define a let method like this:

csharpCopy codelet(:person) { Person.new(name: "John", age: 30) }

Then, in your test case, you can use the person variable like this:

perlCopy codeit "has the correct name" do
  expect(person.name).to eq("John")
end

it "has the correct age" do
  expect(person.age).to eq(30)
end

let!, on the other hand, is also a memoized helper method but it evaluates the expression immediately and memoizes the result. This means that the variable or object is evaluated before each test case that uses it.

For example, if you need to set up a database record before running your test cases, you could use let!:

scssCopy codelet!(:user) { create(:user) }

The create method creates a new user record in the database, and the user variable is available in each test case.

In summary, let and let! are RSpec helper methods that allow you to define variables or objects that can be reused in multiple test cases. let is lazy evaluated and caches its value after first evaluation, while let! is evaluated immediately before each test case that uses it.

Use let to define a memoized helper method. The value will be cached across multiple calls in the same example but not across examples. Note that let is lazy-evaluated: it is not evaluated until the first time
the method it defines is invoked. You can use let! to force the method’s invocation before each example. By default, let is threadsafe, but you can configure it not to be by disabling config.threadsafe, which makes let perform a bit faster.

Use `let` to define memoized helper method

$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

Use `let!` to define a memoized helper method that is called in a `before` hook

$count = 0
RSpec.describe "let!" do
  invocation_order = []

  let!(:count) do
    invocation_order << :let!
    $count += 1
  end

  it "calls the helper method in a before hook" do
    invocation_order << :example
    expect(invocation_order).to eq([:let!, :example])
    expect(count).to eq(1)
  end
end

Arbitrary helper methods

You can define methods in any example group using Ruby’s def keyword or
define_method method. These helper methods are exposed to examples in the
group in which they are defined and groups nested within that group, but not
parent or sibling groups.ScenariosUse a method defined in the same groupGivena file named “example_spec.rb” with:

RSpec.describe "an example" do
  def help
    :available
  end

  it "has access to methods defined in its group" do
    expect(help).to be(:available)
  end
end

WhenI run rspec example_spec.rbThenthe examples should all passUse a method defined in a parent groupGivena file named “example_spec.rb” with:

RSpec.describe "an example" do
  def help
    :available
  end

  describe "in a nested group" do
    it "has access to methods defined in its parent group" do
      expect(help).to be(:available)
    end
  end
end

WhenI run rspec example_spec.rbThenthe examples should all pass

Define helper methods in a module

You can define helper methods in a module and include it in your example
groups using the config.include configuration option. config.extend can be
used to extend the module onto your example groups so that the methods in the
module are available in the example groups themselves (but not in the actual
examples).

You can also include or extend the module onto only certain example groups
by passing a metadata hash as the last argument. Only groups that match the
given metadata will include or extend the module. You can also specify
metadata using only symbols.

Note that examples that match a config.include module’s metadata will also have the module included. RSpec treats every example as having a singleton example group (analogous to Ruby’s singleton classes) containing just the one example.BackgroundGivena file named “helpers.rb” with:

module Helpers
  def help
    :available
  end
end

Scenarios

  1. Include a module in all example groups
  2. Extend a module in all example groups
  3. Include a module in only some example groups
  4. Extend a module in only some example groups
  5. Use symbols as metadata

Include a module in all example groupsGivena file named “include_module_spec.rb” with:

require './helpers'

RSpec.configure do |c|
  c.include Helpers
end

RSpec.describe "an example group" do
  it "has access to the helper methods defined in the module" do
    expect(help).to be(:available)
  end
end

WhenI run rspec include_module_spec.rbThenthe examples should all passExtend a module in all example groupsGivena file named “extend_module_spec.rb” with:

require './helpers'

RSpec.configure do |c|
  c.extend Helpers
end

RSpec.describe "an example group" do
  puts "Help is #{help}"

  it "does not have access to the helper methods defined in the module" do
    expect { help }.to raise_error(NameError)
  end
end

WhenI run rspec extend_module_spec.rbThenthe examples should all passAndthe output should contain “Help is available”Include a module in only some example groupsGivena file named “include_module_in_some_groups_spec.rb” with:

require './helpers'

RSpec.configure do |c|
  c.include Helpers, :foo => :bar
end

RSpec.describe "an example group with matching metadata", :foo => :bar do
  it "has access to the helper methods defined in the module" do
    expect(help).to be(:available)
  end
end

RSpec.describe "an example group without matching metadata" do
  it "does not have access to the helper methods defined in the module" do
    expect { help }.to raise_error(NameError)
  end

  it "does have access when the example has matching metadata", :foo => :bar do
    expect(help).to be(:available)
  end
end

WhenI run rspec include_module_in_some_groups_spec.rbThenthe examples should all passExtend a module in only some example groupsGivena file named “extend_module_in_only_some_groups_spec.rb” with:

require './helpers'

RSpec.configure do |c|
  c.extend Helpers, :foo => :bar
end

RSpec.describe "an example group with matching metadata", :foo => :bar do
  puts "In a matching group, help is #{help}"

  it "does not have access to the helper methods defined in the module" do
    expect { help }.to raise_error(NameError)
  end
end

RSpec.describe "an example group without matching metadata" do
  puts "In a non-matching group, help is #{help rescue 'not available'}"

  it "does not have access to the helper methods defined in the module" do
    expect { help }.to raise_error(NameError)
  end
end

WhenI run rspec extend_module_in_only_some_groups_spec.rbThenthe examples should all passAndthe output should contain “In a matching group, help is available”Andthe output should contain “In a non-matching group, help is not available”Use symbols as metadataGivena file named “symbols_as_metadata_spec.rb” with:

require './helpers'

RSpec.configure do |c|
  c.include Helpers, :include_helpers
  c.extend  Helpers, :extend_helpers
end

RSpec.describe "an example group with matching include metadata", :include_helpers do
  puts "In a group not matching the extend filter, help is #{help rescue 'not available'}"

  it "has access to the helper methods defined in the module" do
    expect(help).to be(:available)
  end
end

RSpec.describe "an example group with matching extend metadata", :extend_helpers do
  puts "In a group matching the extend filter, help is #{help}"

  it "does not have access to the helper methods defined in the module" do
    expect { help }.to raise_error(NameError)
  end
end

WhenI run rspec symbols_as_metadata_spec.rbThenthe examples should all passAndthe output should contain “In a group not matching the extend filter, help is not available”Andthe output should contain “In a group matching the extend filter, help is available”