Instantly share code, notes, and snippets.

Embed
What would you like to do?
Explanation for why `its` will be removed from rspec-3

its isn't core to RSpec. One the of the focuses of RSpec is on the documentation aspects of tests. Unfortunately, its often leads to documentation output that is essentially lies. Consider this spec:

User = Struct.new(:name, :email)

describe User do
  subject { User.new("bob") }
  its(:name) { should == "bob" }
end
$ bin/rspec user_spec.rb --format doc

User
  name
    should == "bob"

Finished in 0.00085 seconds
1 example, 0 failures

Randomized with seed 25153

It's not a true behavior of User#name that it always equals "bob". its generally leads to output that is true in a specific case, but false in the general case, and doesn't help understanding when reading the documentation output.

Those who like its tend to really like it and want to continue to evolve it to do more and more powerful and terse things, such as having it support arguments. We're uncomfortable with making its more and more meta (it's already meta enough!). We were planning on moving its into an external gem when we removed it from rspec-core, and @dnagir has already done that:

https://github.com/dnagir/its

Recently, we've also found some cases where there was some surprising, non-intuitive behavior with example groups that use subject and its:

https://github.com/rspec/rspec-core/pull/768#issuecomment-11918027

I think its gains you terseness at the expense of clarity, and in my judgement, it's a poor tradeoff.

I also feel like rspec-given is a better direction to go with one-liners. If you like one-liners, rspec-given encourages Given/When/Then one-liners, with no example docstrings at all. It's more of a unified vision for this kind of thing than the its method in rspec-core.

Finally, you can read @dchelimsky's thoughts on its from a while ago.

@perlun

This comment has been minimized.

Show comment
Hide comment
@perlun

perlun Jan 1, 2016

Excellent writeup. 👍 Is there a migration guide somewhere? (migrating some rspec 2 tests to 3.x right now).

perlun commented Jan 1, 2016

Excellent writeup. 👍 Is there a migration guide somewhere? (migrating some rspec 2 tests to 3.x right now).

@shadowbq

This comment has been minimized.

Show comment
Hide comment
@shadowbq

shadowbq Jan 20, 2016

Dont point at jim's repo anymore 😢 for rspec-given use https://github.com/rspec-given/rspec-given

shadowbq commented Jan 20, 2016

Dont point at jim's repo anymore 😢 for rspec-given use https://github.com/rspec-given/rspec-given

@dsandstrom

This comment has been minimized.

Show comment
Hide comment
@dsandstrom

dsandstrom Jan 23, 2016

@perlun You probably already found it, but: https://www.relishapp.com/rspec/docs/upgrade . I'm not sure of the best way to migrate its, though. Maybe it { expect(subject.name).to eq "Bob" }.

dsandstrom commented Jan 23, 2016

@perlun You probably already found it, but: https://www.relishapp.com/rspec/docs/upgrade . I'm not sure of the best way to migrate its, though. Maybe it { expect(subject.name).to eq "Bob" }.

@USAWal

This comment has been minimized.

Show comment
Hide comment
@USAWal

USAWal Mar 15, 2016

@myronmarston I don't think that's a problem of its. Actually we need to talk not about successful output, which is rarely investigated, but about failed one.
it { expect(subject.name).to eq "Bob" } - if subject doesn't return 'Bob', then output will notify us that the name should be equal to 'Bob'. The problem still exists even without its.
To get well documented output that make developer free to not open spec file we need to write the next:
it { expect(User.new('bob', 'bobs@mail.com').name)to eq 'bob' }
so the output completely describe what we need to do to fix a bug. I think it's good if you have a simple enough context, that you can put into expectation. But in the most of the cases wee need to describe context once at the beginning, and test against it many times.
I believe, if we start to describe "true behavior" of User#name, we will end up with a complicated description of it

USAWal commented Mar 15, 2016

@myronmarston I don't think that's a problem of its. Actually we need to talk not about successful output, which is rarely investigated, but about failed one.
it { expect(subject.name).to eq "Bob" } - if subject doesn't return 'Bob', then output will notify us that the name should be equal to 'Bob'. The problem still exists even without its.
To get well documented output that make developer free to not open spec file we need to write the next:
it { expect(User.new('bob', 'bobs@mail.com').name)to eq 'bob' }
so the output completely describe what we need to do to fix a bug. I think it's good if you have a simple enough context, that you can put into expectation. But in the most of the cases wee need to describe context once at the beginning, and test against it many times.
I believe, if we start to describe "true behavior" of User#name, we will end up with a complicated description of it

@wrzasa

This comment has been minimized.

Show comment
Hide comment
@wrzasa

wrzasa Mar 19, 2016

So a nice, terse syntax was removed and the problem of misleading output is still there (because its hard to consder it { expect(User.new('bob', 'bobs@mail.com').name)to eq 'bob' } a good idea, even it { expect(subject.name).to eq "Bob" } syntax is not too elegant). We lost nice syntax gaining nothing. Sad. I will rather use https://github.com/rspec/rspec-its

wrzasa commented Mar 19, 2016

So a nice, terse syntax was removed and the problem of misleading output is still there (because its hard to consder it { expect(User.new('bob', 'bobs@mail.com').name)to eq 'bob' } a good idea, even it { expect(subject.name).to eq "Bob" } syntax is not too elegant). We lost nice syntax gaining nothing. Sad. I will rather use https://github.com/rspec/rspec-its

@terryyin

This comment has been minimized.

Show comment
Hide comment
@terryyin

terryyin Mar 21, 2016

I just discovered this new way of spec and you've removed it:-(
I don't see why it's miss leading when it's within a "context".

terryyin commented Mar 21, 2016

I just discovered this new way of spec and you've removed it:-(
I don't see why it's miss leading when it's within a "context".

@wilsonsilva

This comment has been minimized.

Show comment
Hide comment
@wilsonsilva

wilsonsilva Nov 30, 2016

@wrzasa, @dsandstrom You can make it expressive by rewriting it using is_expected + have_attributes matcher:

subject { User.new('bob') }

- it { expect(subject.name).to eq "Bob" }
+ it { is_expected.to have_attributes(name: 'Bob' }

wilsonsilva commented Nov 30, 2016

@wrzasa, @dsandstrom You can make it expressive by rewriting it using is_expected + have_attributes matcher:

subject { User.new('bob') }

- it { expect(subject.name).to eq "Bob" }
+ it { is_expected.to have_attributes(name: 'Bob' }
@meagar

This comment has been minimized.

Show comment
Hide comment
@meagar

meagar Jul 5, 2017

I can't see how have_attributes is any better than the original its. It has more cryptic output and exhibits the same problem removing its was supposed to solve, namely:

its generally leads to output that is true in a specific case, but false in the general case, and doesn't help understanding when reading the documentation output.

How is this:

subject { post account_path(@account), password: 'blah', confirmation: 'foo' }

it { is_expected.to have_attributes(body: a_string_matching('Password confirmation is invalid')) }

> Accounts
>   #update
>     with an invalid password confirmation
>       should have attributes {:body => (a string matching "Password confirmation is invalid")}

better than the its alternative:

its(:body) { is_expected.to match('Password confirmation is invalid') }

> Account
>   #update
>     with an invalid password confirmation
>       body
>         should match "Password confirmation is invalid"

meagar commented Jul 5, 2017

I can't see how have_attributes is any better than the original its. It has more cryptic output and exhibits the same problem removing its was supposed to solve, namely:

its generally leads to output that is true in a specific case, but false in the general case, and doesn't help understanding when reading the documentation output.

How is this:

subject { post account_path(@account), password: 'blah', confirmation: 'foo' }

it { is_expected.to have_attributes(body: a_string_matching('Password confirmation is invalid')) }

> Accounts
>   #update
>     with an invalid password confirmation
>       should have attributes {:body => (a string matching "Password confirmation is invalid")}

better than the its alternative:

its(:body) { is_expected.to match('Password confirmation is invalid') }

> Account
>   #update
>     with an invalid password confirmation
>       body
>         should match "Password confirmation is invalid"
@kevinelliott

This comment has been minimized.

Show comment
Hide comment
@kevinelliott

kevinelliott May 3, 2018

I happen to agree with @meagar.

kevinelliott commented May 3, 2018

I happen to agree with @meagar.

@yemartin

This comment has been minimized.

Show comment
Hide comment
@yemartin

yemartin Jun 4, 2018

Revisiting our use of rspec-its, I ended up here, trying to really understand the decision behind the removal of the its syntax from the core, and to answer a simple questions: should we avoid its, and why?

Unfortunately, the example given in this gist does this issue a disservice: its gets blamed for the bad documentation output, but as others have already pointed out, the expectation can be written without, and this is exactly what I see a lot of people doing now that its has been removed from core:

it { expect(subject.name).to eq "bob" }

With this, the output becomes:

User
  should eq "bob"

If anything, this is worse. So why blame its?

I was interested in the mention of some surprising, non-intuitive behavior with example groups that use subject and its (rspec/rspec-core#768 (comment)). But if I read that thread correctly, it was actually a bug, and it has been fixed since then.

The only thing that kind of makes sense to me is the "uncomfortable with making its more and more meta" comment. I can see that as a valid reason to want to extract it into a separate gem, but still not a reason to avoid using it in its current just-enough-meta form...

I came here for understanding, but found only red herrings, no real answers, and I am still unclear whether its should be considered dangerous and avoided, or if it is perfectly OK to keep using it through rspec-its... My only takeaway is: people will write bad specs, with its or without.

yemartin commented Jun 4, 2018

Revisiting our use of rspec-its, I ended up here, trying to really understand the decision behind the removal of the its syntax from the core, and to answer a simple questions: should we avoid its, and why?

Unfortunately, the example given in this gist does this issue a disservice: its gets blamed for the bad documentation output, but as others have already pointed out, the expectation can be written without, and this is exactly what I see a lot of people doing now that its has been removed from core:

it { expect(subject.name).to eq "bob" }

With this, the output becomes:

User
  should eq "bob"

If anything, this is worse. So why blame its?

I was interested in the mention of some surprising, non-intuitive behavior with example groups that use subject and its (rspec/rspec-core#768 (comment)). But if I read that thread correctly, it was actually a bug, and it has been fixed since then.

The only thing that kind of makes sense to me is the "uncomfortable with making its more and more meta" comment. I can see that as a valid reason to want to extract it into a separate gem, but still not a reason to avoid using it in its current just-enough-meta form...

I came here for understanding, but found only red herrings, no real answers, and I am still unclear whether its should be considered dangerous and avoided, or if it is perfectly OK to keep using it through rspec-its... My only takeaway is: people will write bad specs, with its or without.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment