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.rb
Thenthe 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 thesubject
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.rb
Thenthe 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.rb
Thenthe 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 asexpect(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 removesObject#should
but this isRSpec::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