Weird behavior with should_not_receive

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

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

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.

On Nov 6, 2011, at 4:45 PM, Alex C. 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 :slight_smile:

On Sun, Nov 6, 2011 at 2:55 PM, David C.
[email protected]wrote:

There you go :slight_smile:

You are three times a gentleman, David.