RSpec Newbie: "Update attributes => false" not being recognised
Just starting out with RSpec. Everything is going smoothly, except for one spec with nested controllers.
I'm trying to ensure that when a 'comment' resource (nested under 'post') is updated with invalid parameters, it renders the 'edit' template. I'm struggling to get rspec to recognise the :update_attributes => false trigger. If anyone has any suggestions, they'd be very appreciated. Attempted code below:
def mock_comment(stubs={})
stubs[:post] = return_post
stubs[:user] = return_user
@mock_comment ||= mock_model(Comment, stubs).as_null_object
end
describe "with invalid paramters" dog
it "re-renders the 'edit' template" do
Comment.stub(:find).with("12") { mock_comment(:update_attributes => false) }
开发者_运维技巧 put :update, :post_id => mock_comment.post.id, :id => "12"
response.should render_template("edit")
end
end
And the controller:
def update
@comment = Comment.find(params[:id])
respond_to do |format|
if @comment.update_attributes(params[:comment])
flash[:notice] = 'Post successfully updated'
format.html { redirect_to(@comment.post) }
format.xml { head :ok }
else
format.html { render :action => "edit" }
format.xml { render :xml => @comment.errors, :status => :unprocessable_entity }
end
end
end
And finally, the error:
Failure/Error: response.should render_template("edit")
expecting <"edit"> but rendering with <"">.
Expected block to return true value.
This is quite an interesting problem. A quick fix is to simply replace the block form of Comment.stub
:
Comment.stub(:find).with("12") { mock_comment(:update_attributes => false) }
with an explicit and_return
:
Comment.stub(:find).with("12").\
and_return(mock_comment(:update_attributes => false))
As to why these two forms should produce different results, that's a bit of a head-scratcher. If you play around with the first form you'll see that the mock is actually returning self
instead of false
when the stubbed method is called. That's tells us it hasn't stubbed the method (since it's specified as a null object).
The answer is that when passing in a block, the block is only executed when the stubbed method is called, not when the stub is defined. So when using the block form, the following call:
put :update, :post_id => mock_comment.post.id, :id => "12"
is executing mock_comment
for the first time. Since :update_attributes => false
is not being passed in, the method is not stubbed, and the mock is returned rather than false
. When the block invokes mock_comment
it returns @mock_comment
, which doesn't have the stub.
Contrariwise, using the explicit form of and_return
invokes mock_comment
immediately. It would probably be better to use the instance variable instead of calling the method each time to make the intent clearer:
it "re-renders the 'edit' template" do
mock_comment(:update_attributes => false)
Comment.stub(:find).with("12") { @mock_comment }
put :update, :post_id => @mock_comment.post.id, :id => "12"
response.should render_template("edit")
end
精彩评论