Geo
Geo

Reputation: 96947

Cannot figure out this rspec error

I am testing cache expiration, and when creating a new ProjectInfo, the cache should be expired for projects as well as for ProjectInfos. I have the following test:

    it "creating a new project info should invalidate the cache" do
      2.times { FactoryGirl.create(:project_info) }
      ProjectInfo.should_receive(:all).and_call_original
      Project.should_receive(:all).and_call_original
      Project.fetch_all

      Project.should_not_receive(:all)
      Project.fetch_all
      ProjectInfo.should_not_receive(:all)
      ProjectInfo.fetch_all

      FactoryGirl.create(:project_info)
      Project.should_receive(:all).and_call_original
      Project.fetch_all <- problem line
      ProjectInfo.should_receive(:all).and_call_original
      ProjectInfo.fetch_all
    end

I get the following error:

 1) ProjectInfo Caching creating a new project should invalidate the cache
     Failure/Error: Project.fetch_all

 Project(id:integer, name:string).all({:include => :project_info})
 expected: 0 times with any arguments
   received: 1 time with arguments: ({:include=>:project_info})

The problematic line if the last Project.fetch_all. Why is rspec expecting it 0 times?

Upvotes: 0

Views: 86

Answers (2)

Peter Alfvin
Peter Alfvin

Reputation: 29419

Summary

You can't set a positive expectation on an object after setting a negative expectation on the same object within the same example.

Detail

I don't know that RSpec's behavior is formally defined when setting expectations on something that already has an expectation set. I produced a set of tests, the results of which are shown below, to try and come up with a unified model.

Conceptually, at least, it seems that RSpec places expectations for each object#method into an object#method-specific FIFO queue and then checks the expectations against that queue at each method invocation and at the end of the block. If the expectations for an entry in the queue are "met", then the expectation decremented or removed (if the decrement goes to 0). If the expectations are not met, then the expectation fails.

Given this scenario, placing a "should_receive" after a "should_not_receive" will always fail if the method is invoked, as in your case.

The solution to this problem and the approach intended by RSpec is to break your single example up into multiple examples such that you are not setting expectations on the same object#method pair twice within the same example.

Note that in the examples below, I show the error on the line that RSpec reports the error on. In the case of an invocation that wasn't expected, that will be the line of the invocation. In a case where an expectation for invocation was set but not satisfied by the end of the block, that will be line that set the expectation.

class Foo; end

describe "multiple expectations on same object with single it clause" do
  it "yes, doit" do # succeeds
    Foo.should_receive(:bar)
    Foo.bar
  end
  it "no, yes, doit" do 
    Foo.should_not_receive(:bar)
    Foo.should_receive(:bar)
    Foo.bar # fails, expected 0, received 1
  end
  it "yes, doit, no" do # succeeds
    Foo.should_receive(:bar)
    Foo.bar
    Foo.should_not_receive(:bar)
  end
  it "yes, doit, yes, doit" do # succeeds
    Foo.should_receive(:bar)
    Foo.bar
    Foo.should_receive(:bar)
    Foo.bar
  end
  it "yes, yes, doit, doit" do # succeeds
    Foo.should_receive(:bar)
    Foo.should_receive(:bar)
    Foo.bar
    Foo.bar
  end
  it "yes, yes, doit" do
    Foo.should_receive(:bar)
    Foo.should_receive(:bar) # fails, expected 1, received 0
    Foo.bar
  end
  it "yes, yes" do
    Foo.should_receive(:bar) # fails, expected 1, received 0
    Foo.should_receive(:bar)
  end
  it "yes, no" do
    Foo.should_receive(:bar) # fails, expected 1, received 0
    Foo.should_not_receive(:bar)
  end
  it "no, yes" do
    Foo.should_not_receive(:bar)
    Foo.should_receive(:bar) # fails, expected 1, received 0
  end
  it "yes(2), doit, yes, doit, doit" do # succeeds
    Foo.should_receive(:bar).exactly(2).times
    Foo.bar
    Foo.should_receive(:bar)
    Foo.bar
    Foo.bar
  end
end

Upvotes: 1

jvanbaarsen
jvanbaarsen

Reputation: 339

I think it has something do Project.should_not_receive(:all) fetch_all will probably call all.

Can you try without the Project.should_not_receive(:all)

Upvotes: 0

Related Questions