问题
I am trying to write some RSpec tests to test my app, but I've stumbled on several problems that I can't find any solution. 1) I am trying to test the update action. Here is my code:
it "email is a new one" do
put :update, id: @user, user: FactoryGirl.attributes_for(:user, :email=>"a@b.c")
@user.reload
@user.email.should == "a@b.c"
puts @user.email
end
Here is the UsersController update action:
def update
@user = User.find(params[:id])
respond_to do |format|
if @user.update_attributes(params[:user])
format.html { redirect_to edit_user_path(@user), :notice => "Your settings were successfully updated."}
format.json { head :no_content }
else
format.html { render action: "edit" }
format.json { render json: @user.errors, status: :unprocessable_entity }
end
end
end
Here is the error:
Failure/Error: @user.email.should == "a@b.c"
expected: "a@b.c"
got: "user16@example.com" (using ==)
It's obvious that the test hasn't changed the email of the user. I took the update action tutorial from here: http://everydayrails.com/2012/04/07/testing-series-rspec-controllers.html . Where I could find the solution?
回答1:
Could @user.update_attributes(params[:user]) be failing for a validation reason?
Also, you could ensure that your test and the controller method are interacting with the same ruby object. That's been a gotcha for me in the past. The way I do it is to stub the find method on the class.
it "email is a new one" do
User.stubs(:find).returns(@user)
put :update, id: @user, user: FactoryGirl.attributes_for(:user, :email=>"a@b.c")
@user.reload
@user.email.should == "a@b.c"
puts @user.email
end
This ensures that you're talking about not only the same record, but the same object during the test.
Lastly, I would argue that your test is doing very much for you. You're basically testing update_attributes, which is a core feature and thoroughly tested already. I would focus on testing the controller behavior. Something like this:
let(:user) { FactoryGirl.create(:user) }
describe "PUT #update" do
before(:each) {
User.stubs(:find).returns(user)
}
it "should redirect to the user path on succesful save" do
user.should_receive(:update_attributes).and_return true
put :update, user, {}
response.should redirect_to(edit_user_path(user))
end
it "should render the edit screen again with errors if the model doesn't save" do
user.should_receive(:update_attributes).and_return false
put :update, user, {}
response.should render_template("edit")
end
end
回答2:
I think the argument for put is incorrect.
put, get, delete, post accept three arguments. The first is path, second is params, and the third is options.
In you code you put two params as two arguments, it's incorrect.
put :update, id: @user, user: FactoryGirl.attributes_for(:user, :email=>"a@b.c")
So, change it to
put :update, {id: @user, user: FactoryGirl.attributes_for(:user, :email=>"a@b.c")}
But, wait! your code will work by above change, but there is security hole in your current code. Make sure you'll add authority check in controller code. For example
return unauthorized unless @user == current_user || current_user.role == "admin"
来源:https://stackoverflow.com/questions/17389720/rspec-test-put-update-action