The ‘subject’

Implicitly defined subject

In RSpec, the subject is a way of defining the object that is being tested in a more concise and readable way. It is an implicit definition of an object that is automatically created by RSpec based on the name of the example group or the description of the example.

When you define an example group with a simple name, such as “User”, RSpec automatically creates a subject with the same name as the example group. For example:

describe User do
  # ...
end

In the above example, the subject is automatically created with the name “User”. You can refer to the subject using the subject method:

describe User do
  it "has a name" do
    subject.name = "John"
    expect(subject.name).to eq("John")
  end
end

In the above example, the subject is used to set and get the name of the user object being tested.

If you provide a description for the example, RSpec will use that description to create a more specific subject. For example:

describe "a new user" do
  it "has no name" do
    expect(subject.name).to be_nil
  end
end

In the above example, the subject is automatically created with the name “a new user”. This makes the example more specific and easier to read.

You can also explicitly define the subject using the subject method:

describe User do
  subject { User.new(name: "John") }

  it "has a name" do
    expect(subject.name).to eq("John")
  end
end

In the above example, the subject is explicitly defined as a new instance of the User class with the name “John”. This allows you to customize the subject for a specific example.

In summary, the implicitly defined subject in RSpec is a convenient way to refer to the object being tested in your examples. It allows you to write more readable and concise tests, and helps to keep your code organized and easy to understand.

`subject` exposed in top level group

RSpec.describe Array do
  it "should be empty when first created" do
    expect(subject).to be_empty
  end
end

`subject` in a nested group

RSpec.describe Array do
  describe "when first created" do
    it "should be empty" do
      expect(subject).to be_empty
    end
  end
end

`subject` in a nested group with a different class (innermost wins)

class ArrayWithOneElement < Array
  def initialize(*)
    super
    unshift "first element"
  end
end

RSpec.describe Array do
  describe ArrayWithOneElement do
    context "referenced as subject" do
      it "contains one element" do
        expect(subject).to include("first element")
      end
    end
  end
end

WhenI run rspec nested_subject_spec.rbThenthe examples should all pass

Explicit Subject

In RSpec, you can define the subject explicitly using the subject method. This allows you to create a customized subject for your examples, instead of relying on the default subject that is implicitly defined based on the example group name or description.

Here is an example of how to define an explicit subject:

rubyCopy codeRSpec.describe Calculator do
  subject(:calculator) { Calculator.new }

  it "adds two numbers" do
    result = calculator.add(2, 3)
    expect(result).to eq(5)
  end

  it "subtracts two numbers" do
    result = calculator.subtract(5, 3)
    expect(result).to eq(2)
  end
end

In the above example, we define the subject as an instance of the Calculator class using the subject method with a block that creates a new instance of the class. We also give the subject a name calculator by passing a symbol argument to the subject method.

In each of the example blocks, we refer to the subject using its name calculator. This allows us to write more readable and expressive code that is easier to understand.

Note that you can also define an explicit subject without giving it a name:

rubyCopy codeRSpec.describe Calculator do
  subject { Calculator.new }

  it "multiplies two numbers" do
    result = subject.multiply(2, 3)
    expect(result).to eq(6)
  end
end

In this example, we define the subject as an instance of the Calculator class using the subject method without giving it a name. We can then refer to the subject using the subject method or using the implicit subject reference, which is the same as the example group name or description.

In summary, defining an explicit subject in RSpec allows you to create a customized subject for your examples, which can improve the readability and expressiveness of your tests. You can define the subject using the subject method with a block that creates a new instance of the class and optionally give it a name to make it easier to refer to in your examples.

Use subject in the group scope to explicitly define the value that is returned by the
subject method in the example scope. Note that while the examples below demonstrate how the subject helper can be used as a user-facing concept, we recommend that you reserve it for support of custom matchers and/or extension libraries that hide its use from examples.

A named subject improves on the explicit subject by assigning it a contextually semantic name. Since a named subject is an explicit subject, it still defines the value that is returned by the subject method in the example scope. However, it defines an additional helper method with the provided name. This helper method is memoized. The value is cached across multiple calls in the same example but not across examples. We recommend using the named helper method over subject in examples.

A `subject` can be defined and used in the top level group scope

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

WhenI run rspec top_level_subject_spec.rbThenthe examples should all pass

The `subject` defined in an outer group is available to inner groups

RSpec.describe Array do
  subject { [1, 2, 3] }

  describe "has some elements" do
    it "which are the prescribed elements" do
      expect(subject).to eq([1, 2, 3])
    end
  end
end

The `subject` is memoized within an example but not across examples

Note: This scenario shows mutation being performed in a subject definition block. This
behavior is generally discouraged as it makes it more difficult to understand the specs.
This is technique is used simply as a tool to demonstrate how the memoization occurs.

RSpec.describe Array do
  # This uses a context local variable. As you can see from the
  # specs, it can mutate across examples. Use with caution.
  element_list = [1, 2, 3]

  subject { element_list.pop }

  it "is memoized across calls (i.e. the block is invoked once)" do
    expect {
      3.times { subject }
    }.to change{ element_list }.from([1, 2, 3]).to([1, 2])
    expect(subject).to eq(3)
  end

  it "is not memoized across examples" do
    expect{ subject }.to change{ element_list }.from([1, 2]).to([1])
    expect(subject).to eq(2)
  end
end

The `subject` is available in `before` hooks

RSpec.describe Array, "with some elements" do
  subject { [] }

  before { subject.push(1, 2, 3) }

  it "has the prescribed elements" do
    expect(subject).to eq([1, 2, 3])
  end
end

WhenI run rspec before_hook_subject_spec.rbThenthe examples should all pass

Helper methods can be invoked from a `subject` definition block

RSpec.describe Array, "with some elements" do
  def prepared_array
    [1, 2, 3]
  end

  subject { prepared_array }

  it "has the prescribed elements" do
    expect(subject).to eq([1, 2, 3])
  end
end

Use the `subject!` bang method to call the definition block before the example

RSpec.describe "eager loading with subject!" do
  subject! { element_list.push(99) }

  let(:element_list) { [1, 2, 3] }

  it "calls the definition block before the example" do
    element_list.push(5)
    expect(element_list).to eq([1, 2, 3, 99, 5])
  end
end

Use `subject(:name)` to define a memoized helper method

Note: While a global variable is used in the examples below, this behavior is strongly
discouraged in actual specs. It is used here simply to demonstrate the value will be
cached across multiple calls in the same example but not across examples.Givena file named “named_subject_spec.rb” with:

$count = 0

RSpec.describe "named subject" do
  subject(:global_count) { $count += 1 }

  it "is memoized across calls (i.e. the block is invoked once)" do
    expect {
      2.times { global_count }
    }.not_to change{ global_count }.from(1)
  end

  it "is not cached across examples" do
    expect(global_count).to eq(2)
  end

  it "is still available using the subject method" do
    expect(subject).to eq(3)
  end

  it "works with the one-liner syntax" do
    is_expected.to eq(4)
  end

  it "the subject and named helpers return the same object" do
    expect(global_count).to be(subject)
  end

  it "is set to the block return value (i.e. the global $count)" do
    expect(global_count).to be($count)
  end
end

Use `subject!(:name)` to define a helper method called before the example

RSpec.describe "eager loading using a named subject!" do
  subject!(:updated_list) { element_list.push(99) }

  let(:element_list) { [1, 2, 3] }

  it "calls the definition block before the example" do
    element_list.push(5)
    expect(element_list).to eq([1, 2, 3, 99, 5])
    expect(updated_list).to be(element_list)
  end
end

One-liner syntax

RSpec supports a one-liner syntax for setting an expectation on thesubject. RSpec will give the examples a doc string that is auto-generated from the matcher used in the example. This is designed specifically to help avoid duplication in situations where the doc string and the matcher used in the example mirror each other exactly. When used excessively, it can produce documentation output that does not read well or contribute to understanding the object you are describing.

This comes in two flavors:

  • is_expected is defined simply as expect(subject) and is designed for when you are using rspec-expectations with its newer expect-based syntax.
  • should was designed back when rspec-expectations only had a should-based syntax. However, it continues to be available and work even if the :should syntax is disabled (since that merely removes Object#should but this is RSpec::Core::ExampleGroup#should).

In RSpec, one-liner syntax is a shorthand way of writing a simple test without having to use a full block syntax. It allows you to write concise and readable tests with minimal boilerplate code.

Here’s an example of using one-liner syntax in RSpec:

rubyCopy codeRSpec.describe Calculator do
  describe "#add" do
    it { expect(Calculator.new.add(2, 3)).to eq(5) }
    it { expect(Calculator.new.add(10, 20)).to eq(30) }
  end
end

In the above example, we define a test for the add method of the Calculator class using one-liner syntax. Instead of using a full block syntax with an expect statement, we write the expectation directly as an expression using curly braces {}.

Note that you can also use the is_expected method with one-liner syntax to refer to the subject of the example:

rubyCopy codeRSpec.describe Calculator do
  describe "#add" do
    subject { Calculator.new }

    it { is_expected.to respond_to(:add) }
    it { is_expected.to respond_to(:subtract) }
  end
end

In this example, we define the subject as an instance of the Calculator class using the subject method. We then use one-liner syntax with the is_expected method to write simple tests that check if the subject responds to certain methods.

One-liner syntax can make your tests more concise and easier to read, especially for simple tests that don’t require a lot of setup or complex expectations. However, it’s important to use it judiciously and avoid using it for tests that require more complex logic or multiple expectations.

Notes:

  • This feature is only available when using rspec-expectations.
  • Examples defined using this one-liner syntax cannot be directly selected from the command line using the --example option.

Implicit subject example

Givena file named “example_spec.rb” with:

RSpec.describe Array do
  describe "when first created" do
    # Rather than:
    # it "should be empty" do
    #   subject.should be_empty
    # end

    it { should be_empty }
    # or
    it { is_expected.to be_empty }
  end
end

Examples

RSpec.describe Array do
  describe "with 3 items" do
    subject { [1,2,3] }
    it { should_not be_empty }
    # or
    it { is_expected.not_to be_empty }
  end
end