4
votes

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=>"[email protected]")
            @user.reload
            @user.email.should == "[email protected]"
            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 == "[email protected]"
       expected: "[email protected]"
            got: "[email protected]" (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?

2
You're assigning the factory to the contact, not to the user... Is it meant to be that way?weltschmerz
Sorry. Forgot to change it. I've written and copied it several times in order to avoid typos. But the problem still exist.Kert

2 Answers

5
votes

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=>"[email protected]")
  @user.reload
  @user.email.should == "[email protected]"
  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
3
votes

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=>"[email protected]")

So, change it to

put :update, {id: @user, user: FactoryGirl.attributes_for(:user, :email=>"[email protected]")}

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"