views:

167

answers:

2

I'm writing an rspec scenario thats failing with:

 (#<User:0x1056904f0>).update_attributes(#<RSpec::Mocks::ArgumentMatchers::AnyArgMatcher:0x105623648>)
     expected: 1 time
     received: 0 times

users_controller_spec.rb:

describe "Authenticated examples" do
  before(:each) do
    activate_authlogic
    @user = Factory.create(:valid_user)
    UserSession.create(@user)
  end

describe "PUT update" do
    it "updates the requested user" do
      User.stub!(:current_user).and_return(@user)
      @user.should_receive(:update_attributes).with(anything()).and_return(true)
      put :update, :id => @user , :current_user => {'email' => 'Trippy'}
      puts "Spec Object Id : " + "#{@user.object_id}"
 end

users_controller.rb:

def update
  @user = current_user
  puts "Controller Object ID is : " + "#{@user.object_id}"

  respond_to do |format|
    if @user.update_attributes(params[:user])
      format.html { redirect_to(root_url, :notice => 'Successfully updated profile.') }
      format.xml  { head :ok }
    else
      format.html { render :action => "edit" }
      format.xml  { render :xml => @user.errors, :status => :unprocessable_entity }
    end
  end
end

user.rb - factories

Factory.define :valid_user, :class => User do |u|
  u.username "Trippy"
  u.password "password"
  u.password_confirmation "password"
  u.email "[email protected]"
  u.single_access_token "k3cFzLIQnZ4MHRmJvJzg"
  u.id "37"
end
+2  A: 

I think you're confusing stubs with message expectations. The line

User.should_receive(:find)

tells Rspec to expect the User model to receive a find message. Whereas:

User.stub!(:find)

replaces the find method so that the test can pass. In your example the thing you're testing is whether update_attributes is called successfully, so that ought to be where the message expectation goes, and the job of all the other testing code is just to set up the prerequisites.

Try replacing that line with:

User.stub!(:find).and_return(@user)

Note that find returns the object, not just its id. Also, note that stubbing out find here serves only to speed things up. As written the example gets through should_receive(:find) successfully, and that is happening because you're using Factories to create users in the test database. You could take the stub out and the test should still work, but at the cost of hitting the database.

Another tip: if you're trying to figure out why a controller test isn't working, sometimes it's helpful to know if it is being blocked by before filters. You can check for this with:

controller.should_receive(:update)

If that fails, the update action is not being reached, probably because a before filter has redirected the request.

zetetic
The stub cleared up the find error. Makes so much sense. The update error did fail, so I moved that authenticated examples before() method for each child declaration ( GET , PUT, POST, DELETE ) .But it still fails on the second error, ` (#<User:0x10564d3a8>).update_attributes({"username"=>"Trippy"}) expected: 1 time received: 0 times`
Trip
It's possible you're not getting the exact hash param you're expecting. Try changing it to `should_receive(:update_attributes).with(anything()).and_return(true)`
rspeicher
Ah! So happy I know that syntax. But alas, it returns the same error. So I assume then that its not updating or being called right. Just not entirely sure why.
Trip
Hmm. I'm not sure what's going on there. Maybe it's time to fire up the debugger and see where the request is being intercepted.btw, not to confuse things, but it looks like your controller doesn't do a find on User at all. You set @user from `current_user` instead of using the ID in the params. That's fine if it's what you intended.
zetetic
Ah! So would you change `put :update, :id => "1" , :user => {'email' => 'Trippy'}` * to * `put :update, :id => "1" , :current_user => {'email' => 'Trippy'}`? It doesn't work, but something along those lines?
Trip
No, what I'm saying is that the ID you pass in the params hash is just being ignored. Eg. if the current user posted a form with the ID of another user, it would update the current user, not the other user. If that's what you intended, it's fine. However it's a little confusing to use a resourceful path when the ID is being discarded.
zetetic
Ah great point. I updated my scenario and error above. Thanks Ze!
Trip
+2  A: 

Authlogic's standard helper methods like current_user don't call User.find directly. I believe it does current_user_session.user, where current_user_session calls UserSession.find, so you're not calling User.find directly. You could do some fancy chain stubbing there, but my suggestion is just to add this to your controller spec instead of what you're currently stubbing:

stub!(:current_user).and_return(@user)

In RSpec2 you might have to do

controller.stub!(:current_user).and_return(@user)

Edit: This should be your whole spec file:

describe "Authenticated examples" do
  before(:each) do
    activate_authlogic
    @user = Factory.create(:valid_user)
    UserSession.create(@user)
  end

describe "PUT update" do

  describe "with valid params" do
    it "updates the requested user" do
      stub!(:current_user).and_return(@user)
      @user.should_receive(:update_attributes).with(anything()).and_return(true)
      put :update, :id => @user , :current_user => {'email' => 'Trippy'}
    end
 end
rspeicher
Alright. I concede. But why is the update_attributes not passing?
Trip
You're setting the expectation for `update_attributes` on the `@user` instance variable you created in the spec, but if your `User.find` stub isn't returning that **exact** instance variable, the user it *does* return doesn't have the expectation set on it. Try adding `puts @user.object_id` in the controller and in the spec and see if they differ.
rspeicher
Not sure how to simultaneously get those puts statement. For the controller, I added puts @user.obect_id, but where would I put that in the spec? or how I should say? it doesn't return the puts statement from i run spec.
Trip
Adding a puts anywhere inside the `it ... do` block should display the output when you run spec, either through rake or just `spec` or `rspec`
rspeicher
Trip
So the real question now is, 'How do I stub the same isntance that I'm checking for?`
Trip
Did you stub `current_user`? I think that should've done it.
rspeicher
User.stub!(:current_user).and_return(@user) just like its written above. Thats exactly the way my code is written now.
Trip
I updated my answer. you should just be calling `stub!(:current_user).and_return(@user)`, because you're stubbing a method technically defined inside the current controller. As I said earlier if you're on RSpec2 or if that doesn't work, you might have to do `controller.stub!(......)`
rspeicher
Nope still returns original error : `(#<User:0x1056904f0>).update_attributes(#<RSpec::Mocks::ArgumentMatchers::AnyArgMatcher:0x105623648>) expected: 1 time received: 0 times`
Trip
If you re-add the `puts @user.object_id` stuff, they're still different?
rspeicher
controller object id is : 2192576140Spec Object Id : 2192865920.. Different ;D
Trip
I'm concerned that you apparently updated your post with the latest code but it still says `User.stub!(:current_user).and_return(@user)`, which isn't what I have in my post.
rspeicher
!ah. controller.stub! did it. why though?
Trip
It's explained in my comment where I said I updated my answer.
rspeicher
Thanks again rspeicher, I can't thank you enough. Learning RSpec is quite difficult for me. :D
Trip