Forum: RSpec Weird behavior with should_not_receive

Posted by David Hofer (Guest)
on 2011-11-06 18:58
(Received via mailing list)
I recently saw a test passing when it should have failed, because the
person who wrote it used should_not_receive instead of
should_receive.  Here is a simple example illustrating the behavior:

class MyTest
  def foo
    puts "hey"
  end

  def bar
    foo
  end
end

describe MyTest do
  it "passes but should fail" do
    subject.should_not_receive(:foo).once
    subject.bar
  end
end

If I remove the ".once" the test fails, as I would expect.

Is this intended behavior?  It seems really weird to me.

I am seeing this with rspec 1.3.2 and rspec-rails 1.3.4.

Thanks,
David
Posted by David Chelimsky (Guest)
on 2011-11-06 21:51
(Received via mailing list)
On Nov 1, 2011, at 12:59 PM, David Hofer wrote:

>    foo
> If I remove the ".once" the test fails, as I would expect.
>
> Is this intended behavior?  It seems really weird to me.
>
> I am seeing this with rspec 1.3.2 and rspec-rails 1.3.4.

It is really weird, but it's also a misunderstanding of the API.

should_receive(:foo) defaults to an expectation of 1 time. The object 
then exposes methods like once, twice, exactly(3).times to 
specify/modify the expectation:

    foo.should_receive(:bar).once
    foo.should_receive(:bar).twice
    foo.should_receive(:bar).exactly(3).times

Yes, I was sorely tempted to support 
foo.should_receive(:bar).three_times_a_lady when we added all that, but 
I refrained. Now that Siri will reenact the entire "Who's on first?" 
routine, I'm reconsidering.

That aside, to specify that a message would not be received, we used to 
have to write:

    foo.should_receive(:bar).exactly(0).times

We later added foo.should_not_receive(:bar) as a shorter, more 
expressive version of that.

So, since methods like once, twice, exactly(n).times, at_least(n).times 
and at_most(n).times all modify the constraint, it turns out that they 
could be used together, like this:

    foo.should_receive(:bar).once.twice.exactly(3).times

In this case, it would expect :bar 3 times, because the last 
modification wins.

Of course you would never do that deliberately, and in my 5 1/2 years 
running this project this is the first time I've ever seen any issue w/ 
this, but that is actually not prevented. Therefore, the following are 
equivalent:

    foo.should_receive(:bar).exactly(0).times.once
    foo.should_not_receive(:bar).once

Hope that helps you to understand the problem. In terms of what we 
can/will do about it, I don't really think we'll do anything about it 
but document it better. It would require too much work to solve this 
without breaking other things, and it turns out that mocha, flexmock, 
and RR all have the same issue:

    foo.expects(:bar).never.once
    flexmock(foo).should_receive(:bar).never.once
    mock(foo).bar.never.once

Cheers,
David
Posted by Alex Chaffee (alexch)
on 2011-11-06 23:49
(Received via mailing list)
Double negatives are not unconfusing.

Not unlike chaining mutable decorator objects.

(I was tempted to say "non-immutable" but that would chain the jokes)

btw with .once and .twice, why not .thrice? Lady or no.
Posted by David Chelimsky (Guest)
on 2011-11-07 00:22
(Received via mailing list)
On Nov 6, 2011, at 4:45 PM, Alex Chaffee wrote:

> Double negatives are not unconfusing.
>
> Not unlike chaining mutable decorator objects.
>
> (I was tempted to say "non-immutable" but that would chain the jokes)
>
> btw with .once and .twice, why not .thrice? Lady or no.

module Thrice
  def thrice(&block)
    exactly(3).times &block
  end
end

RSpec::Mocks::MessageExpectation.send :include, Thrice

There you go :)
Posted by Alex Chaffee (alexch)
on 2011-11-07 14:13
(Received via mailing list)
On Sun, Nov 6, 2011 at 2:55 PM, David Chelimsky 
<dchelimsky@gmail.com>wrote:

> There you go :)
>

You are three times a gentleman, David.
Please log in before posting. Registration is free and takes only a minute.
Existing account (Switch to SSL-encrypted connection)
NEW: Do you have a Google/GoogleMail or Yahoo account? No registration required!
Log in with Google account | Log in with Yahoo account
No account? Register here.