How to properly use subject?

20 views
Skip to first unread message

Alexander Popov

unread,
Nov 28, 2018, 11:37:41 AM11/28/18
to rspec
Hello,

Given a class:

class Person
 
def name
   
'John'
 
end

 
def age
   
20
 
end
end

What is the proper way to use subject?

Version 1:

RSpec.describe Person do
  subject
(:person) { Person.new }

  it
'#name' do
    expect
(person.name).to eq 'John'
 
end

  it
'#age' do
    expect
(person.age).to eq 20
 
end
end

Version 2:

RSpec.describe Person do
  let
(:person) { Person.new }

  describe
'#name' do
    subject
{ person.name }

    it
{ is_expected.to eq 'John' }
 
end

  describe
'#age' do
    subject
{ person.age }

    it
{ is_expected.to eq 20 }
 
end
end

I think that most people have the very deeply-rooted belief that subject necessarily needs to hold an instance of the class under test (Version 1). This might partly come from this sentence in the documentation:

https://relishapp.com/rspec/rspec-core/v/3-8/docs/subject/implicitly-defined-subject

> If the first argument to the outermost example group is a class, an instance of that class is exposed to each example via the subject method.

However, I think that here the documentation simply describes what the default behavior is and doesn't imply that it always has to be that way. My understanding is that subject conceptually represents the thing under testing. If we want to assert the result of a method call, than subject better hold this result. For example, given this calss:

class Person
  attr_accessor
:name

 
def initialize(name)
   
@name = name
 
end

 
def greeting
   
"Hi, #{name}!"
 
end
end

I find it more appropriate to use subject like so:

RSpec.describe Person do
  describe
'#greeting' do
    context
'when the person is called John' do
      subject
{ Person.new('John').greeting }

      it
{ is_expected.to eq 'Hi, John!' }
   
end

    context
'when the person is called Merry' do
      subject
{ Person.new('Merry').greeting }

      it
{ is_expected.to eq 'Hi, Merry!' }
   
end
 
end
end

instead of:

RSpec.describe Person do
  subject
(:person) { Person.new(name) }

  describe
'#greeting' do
    context
'when the person is called John' do
      let
(:name) { 'John' }

      it
{ expect(subject.greeting).to eq 'Hi, John!' }
   
end

    context
'when the person is called Merry' do
      let
(:name) { 'Merry' }

      it
{ expect(subject.greeting).to eq 'Hi, Merry!' }
   
end
 
end
end

I'd like to know what is the recommended way of using subject. Thank you very much.

belgoros

unread,
Feb 5, 2019, 9:30:35 AM2/5/19
to rspec
I'd repons by a citation from "Effective testing with RSpec 3.0" book:

The subject method defines how to build the object were testing. RSpec gives us is_expected as shorthand for expect(subject). The phrase it is_expected reads nicely in each spec here, though in larger projects we tend to favor more explicit let constructs for claritys sake 

Jon Rowe

unread,
Feb 5, 2019, 10:43:05 AM2/5/19
to rs...@googlegroups.com
Hi Alexander

The recommended use of `subject` has evolved over time, so you’ll find all of the examples you mention in use in various projects.

Our advice would be to use subject to draw attention to your “subject under test” but also to give it a descriptive name. We would also advice using that name, subject doesn’t mean much to a reader in context. We also advice not using the implicit subject and to always have a doc string. What that means in your tests depends on your implementation, if you are doing unit style tests such as a person name, the subject is really the name, not the person.

But for example, this is how I would describe #name in your example.

```
class Person
  def initialize(first_name, last_name)
    @first_name = first_name
    @last_name = last_name
  end

  def name
    [@first_name, @last_name].join(“ “)
  end
end

RSpec.describe “Person” do
  describe “#name” do
    subject(:name) { Person.new(“Jon”, “Rowe”).name }
    it “combines first and last names” do
      expect(name).to eq “Jon Rowe"
    end
  end
end
```

Regards

Jon Rowe
---------------------------

avp...@gmail.com

unread,
Feb 6, 2019, 5:33:14 AM2/6/19
to rspec
Hello Jon,

Thank you very much for your answer! In unit testing I also tend to:
  • assign the result of the method under test to subject;
  • name the subject
Which is exactly what you show in your example. I also do something similar for request specs:

```
subject(:create_user) { post users_path, params: { user: {name: 'John' } } }

and then

expect { create_user }.to change { User.count }.by(1)
```
Reply all
Reply to author
Forward
0 new messages