Skip to content

Instantly share code, notes, and snippets.

@puyo
Created November 15, 2013 03:01
Show Gist options
  • Save puyo/7478418 to your computer and use it in GitHub Desktop.
Save puyo/7478418 to your computer and use it in GitHub Desktop.

RSpec 3 syntax is more verbose.

About twice as many characters to type to stub something:

obj.stub(client: client)                          # old
allow(obj).to receive(:client).and_return(client) # new
allow(obj).to receive(client: client)             # possible? still much longer
allow(obj, client: client)                        # I might wrap it in this

More characters to type for every single assertion (multiply by number of assertions desired):

obj.should == value
expect(obj).to eq value          # +4 characters

obj.should_receive(:method)
expect(obj).to receive(:method)  # +4 characters

expect(obj, eq value)            # +2 characters and doesn't read as well
# old frowned upon "its" extension, but look how few keystrokes, and how DRY
its(:body) { should include(text) }

# new (via transpec)
describe '#body' do
  subject { super().body }               # jon would disapprove of this anyway?
  it { is_expected.to include(text) }    # why is there one _ and one . (says my don't make me think brain)
end

its(:body) { is_expected.to include(text) }

I think there are more examples too.

But you know, I don't so much mind the extra typing, it's probably having to type ( that bugs me. That character is significantly harder to type than . and ==.

Was the less metaprogramming, less magical solution worth it?

I have no easy solution to this by the way. Just trying to communicate something I expressed in 140 characters better.

@puyo
Copy link
Author

puyo commented Nov 15, 2013

Firstly, thank you for such a thorough response! I know you're busy. I really appreciate it. Respect+++.

You say that your its example is so DRY, but there is no duplication of knowledge in my version. It's not any less DRY. Your version is definitely fewer characters, but that has nothing to do with DRY.

The lack of DRYness of which I speak is between the specdoc and the test body.

it 'includes the text in the body' do
  # I'd probably use a more intention-revealing name than subject, but your example doesn't show that context
  expect(subject.body).to include(text)
end

The word body is included twice. The concept is included twice (subject.body and 'the body'). Compare it to:

its(:body) { should include(text) }

This example is tiny and contrived but consider a more fully featured example and count the number of times the method under test is repeated either in the test body or in the specdoc. Imagine it is a long method name. I complain in more detail here: https://github.com/rails-oceania/rspec-subject_call/blob/master/README.md#the-situation

The same situation happens with contexts if you use them to describe specific cases rather than generalised assertions.

# specific example
context 'when provided with 23 October 2013' do
  let(:input) { Date.new(2013, 10, 23) }
  it 'should print 2013-10-23'
end

# generalised example
context 'when provided with a date object' do
  let(:input) { Date.new(2013, 10, 23) }
  it 'should print the date in yyyy-mm-dd format'
end

I'm not sure which of these two flavours you think is better. I'm not sure which is better, to be honest. But I have noticed that sometimes my contexts and the first line inside of their blocks sometimes feel very un-DRY. This is not related to RSpec 3 versus RSpec 2 syntax, though.

It's this way because is_expected is defined simply as expect(subject)...

Yes I know. It will probably just take a little getting used to. Are any of these better (or worse? :P) ?

expect_it { to eq 10 }
expect { it.not_to eq 10 }
expect { it.to eq 10 }

The new syntax is also much more consistent.

You're right. And now that I think more about it, this is a tremendous boon to making RSpec easier to learn.

I would like to figure out ways to incrementally get out of the business of writing code for tests and get more into the business of using a Ruby DSL to specify what I expect. In my mind, that means moving away from test names with test bodies that sort of resemble OOP methods and towards more declarative syntax with procedural-based fallback options for tricky tests. I believe this kind of approach would make RSpec much more succinct. I'm yet to figure out what the declarative syntax would actually be, though. ;-)

@myronmarston
Copy link

The lack of DRYness of which I speak is between the specdoc and the test body.

Right. I'm actually very much in favor of using a one-liner when the doc string you would write is identical to the one RSpec can generate for you in the one-liner. IMO, this is what the one-liner feature was intended for and is a great use for it.

However, I personally get a lot of value out of longer, more intention-revealing doc strings. For example, here are some from the code base I work on at my job:

it 'ignores campaign_id by default (since it is used by sharding but not endpoint models)'
it 'logs sauron reload errors to a log file (since they will generally be transient)'
it "retries a few times since we do not want S3 and redis to get out of sync"
it 'can handle there being no title (since title is optional in the packrat schema)'
it 'raises an error if given a namespaced model, since we want to subclass the top-level model'

These longer doc strings help explain the why for a particular behavior, and help guide future decisions when we consider changing a behavior. (We change it, watch a spec break, and read the doc string to remind us the reason why it was there in the first place.)

I'm not sure which of these two flavours you think is better.

Hard to answer that kind of thing without seeing the full example.

Yes I know. It will probably just take a little getting used to. Are any of these better (or worse? :P) ?

IMO they are worse. Long time users to RSpec understand that self in describe is different from self in it -- but new user don't generally know that. The examples you showed there all define an alternate expect and/or an alternate it that does something else from what they normally do. It could work because you've switched the example vs. group context (e.g. you're using expect in the group and it in the example), but I think it'll cause confusion. On top of that, it requires changes both to the example group API (e.g. to add a method like expect or expect_it) and to the example API (e.g. to add to or it). I like the simplicity of is_expected: it is exactly equal to expect(subject) and is very easy to understand. It doesn't require new methods in two contexts. It doesn't use an existing method name for something new.

FWIW, I originally proposed something very similar when I first proposed adding the expect syntax:

rspec/rspec-expectations#119 (comment)

I'm glad we didn't go that route; I find is_expected to be much simpler and less confusing.

You're right. And now that I think more about it, this is a tremendous boon to making RSpec easier to learn.

That's one thing I hope!

I would like to figure out ways to incrementally get out of the business of writing code for tests and get more into the business of using a Ruby DSL to specify what I expect. In my mind, that means moving away from test names with test bodies that sort of resemble OOP methods and towards more declarative syntax with procedural-based fallback options for tricky tests. I believe this kind of approach would make RSpec much more succinct. I'm yet to figure out what the declarative syntax would actually be, though. ;-)

If you want more DSL, give rspec-given a try. It layers some additional DSL constructs on top of DSL and, IMO, is a more well-thought out, consistent approach than rspec-core's one-liners or its.

Personally, I find I prefer the simplicity of starting with the simpler constructs (describe, it) and then, as need arises, using some of the additional constructs like let, before, etc (and, occasionally, a one-liner or its).

@myronmarston
Copy link

BTW, please ping me on twitter when you reply. I don't seem to get email notification for gist comments. (Not sure why).

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