Reputation: 96947
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
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
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