Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
rspec expect-syntax, stand-alone operators, and you

rspec expect syntax and stand-alone operator matches

I recently started a new project using rspec's newer (and soon to be default) expect syntax, and encountered this error:

expect(5).to == 5
ArgumentError: The expect syntax does not support operator matchers, so you must pass a matcher to `#to`

"Why'd they take out operator matches? I've grown quite accustomed to them!", I thought. Digging around, the source of this change started in pull request 119 citing issue 138 as one of the root causes. Here's what's actually happening:

5.should == 5 actually evaluates to (5.should).==(5). Using pry from an example:

[35] pry(#<RSpec::Core::ExampleGroup::Nested_1::Nested_1>)> 5.should
=> #<RSpec::Matchers::BuiltIn::PositiveOperatorMatcher:0x00000002fb9c38 @actual=5>
[36] pry(#<RSpec::Core::ExampleGroup::Nested_1::Nested_1>)> ls _
RSpec::Matchers::OperatorMatcher#methods: !=  !~  <  <=  ==  ===  =~  >  >=  description  fail_with_message
RSpec::Matchers::BuiltIn::PositiveOperatorMatcher#methods: __delegate_operator
instance variables: @actual

On the other hand, the preferred route of 5.should eq(5) actually evaluates to (5.should(eq(5)). From pry:

[37] pry(#<RSpec::Core::ExampleGroup::Nested_1::Nested_1>)> eq(5)
=> #<RSpec::Matchers::BuiltIn::Eq:0x0000000308ba58 @expected=5>
[38] pry(#<RSpec::Core::ExampleGroup::Nested_1::Nested_1>)> 5.should(_)
=> true

Spot the difference? With ==, should is called with no arguments, and the operator is run on the object returned by should. With eq, a matcher is instantiated and passed as the first argument to should.

Why is this an issue? Look at that issue 138, and you'll see that it's plausibly easy to accidentally break your assertion onto two lines, such that no actual assertion happens:

[41] pry(#<RSpec::Core::ExampleGroup::Nested_1::Nested_1>)> 5.should
=> #<RSpec::Matchers::BuiltIn::PositiveOperatorMatcher:0x0000000347a0d8 @actual=5>
[42] pry(#<RSpec::Core::ExampleGroup::Nested_1::Nested_1>)> eq(4)
=> #<RSpec::Matchers::BuiltIn::Eq:0x00000002a74570 @expected=4>

With the expect syntax, it was decided to make the .to method require a matcher object to be passed in as a parameter. Stand alone operator matches no longer are viable because operator methods need an explicit receiver.

[44] pry(#<RSpec::Core::ExampleGroup::Nested_1::Nested_1>)> def eq(value)
[44] pry(#<RSpec::Core::ExampleGroup::Nested_1::Nested_1>)*   puts 'hi from the matcher'
[44] pry(#<RSpec::Core::ExampleGroup::Nested_1::Nested_1>)* end
=> nil
[45] pry(#<RSpec::Core::ExampleGroup::Nested_1::Nested_1>)> def ==(value)
[45] pry(#<RSpec::Core::ExampleGroup::Nested_1::Nested_1>)*   puts 'hi from the matcher'
[45] pry(#<RSpec::Core::ExampleGroup::Nested_1::Nested_1>)* end
=> nil
[46] pry(#<RSpec::Core::ExampleGroup::Nested_1::Nested_1>)> eq(5)
hi from the matcher
=> nil
[47] pry(#<RSpec::Core::ExampleGroup::Nested_1::Nested_1>)> ==(5)
SyntaxError: unexpected tEQ, expecting $end
==(5)
  ^

If you want this to work, the expect-style call would have to read: expect(5).to self.==(5), and that's much harder to read and write than using the equivalent eq, gt, etc.

However, there's still a work around! Although "stand alone" operator matchers are out, the be matcher has operator matches on it:

[48] pry(#<RSpec::Core::ExampleGroup::Nested_1::Nested_1>)> ls be
RSpec::Matchers::Pretty#methods: _pretty_print  name  name_to_sentence  split_words  to_sentence  to_word  underscore
RSpec::Matchers::BuiltIn::BaseMatcher#methods: actual  description  diffable?  expected  match_unless_raises  matches?  rescued_exception
RSpec::Matchers::BuiltIn::Be#methods: <  <=  ==  ===  =~  >  >=  failure_message_for_should  failure_message_for_should_not  match

So, even with the expect syntax, you can still write: expect(5).to be > 4

@mfdorst

This comment has been minimized.

Copy link

@mfdorst mfdorst commented Dec 18, 2013

Thank you, this was helpful to me.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.