2
votes

I am using Devise for authentication obviously and what I am trying to do is test that a method is invoked on the user object. So my spec looks like this:

it "should retrieve something for user" do
  @user = Factory.create(:user) 
  sign_in @user
  @user.expects(:something)
  get :manage
end

the problem I have is the expects fails unless I do:

it "should retrieve something for user" do
  @user = Factory.create(:user) 
  sign_in @user
  controller.stubs(:current_user).returns @user
  @user.expects(:something)
  get :manage
end

faking out the current_user call on the controller seems hokey if sign_in @user is a devise test helper. after digging in the debugger also it appears when not faking out current_user the @user is really being returned so Im not sure why the expectation is not met.

Ideas?

1

1 Answers

3
votes

The current user is serialized before storing in the session; likely, for an ActiveRecord User model, it's storing the user's id:

https://github.com/plataformatec/devise/blob/master/lib/devise/test_helpers.rb#L49

This means that when your controller fetches the user again:

https://github.com/plataformatec/devise/blob/master/lib/devise/controllers/helpers.rb#L47-49

it's finding the user_id in the session, and re-fetching the User from the database.

This means that the User object returned by #current_user in the controller is a different Ruby object than the @user you pass into #sign_in in your test, so stubs and expectations from one object aren't attached to the other.

I haven't used Devise/Warden extensively, but am pretty sure this is what's going on. You can try printing out #object_id on the two instances to confirm this.

Picky semantics update Feb 2014:

Stubbing #current_user on your controller is a fine approach here, depending on your definition of the "boundary" of the system under test (SUT) -- i.e. your controller. Assuming the #current_user method is defined by Devise and mixed into your controller by Devise, you could argue that #current_user is external to your SUT and therefore fair game for stubbing.

You could alternatively stub the underlying layer that Devise is accessing (ActiveRecord), say by stubbing User.find to return your @user object. This means your spec is testing both your action implementation as well as the Devise implementation of #current_user, so the spec would fail if either of those things changed later.

Let's say Devise uses User.find(args), and say you stub that method, and then a later version of Devise changed to use User.where(args).first() - your code hasn't changed, but the underlying library has, and your spec fails. Thinking very generally about this idea, there are times when you'd probably like that behavior (think about mocking a raw HTTP response with e.g. WebMock instead of stubbing Net::HTTP methods so you could later swap http libraries), and times you wouldn't (maybe this Devise question counts as one).