views:

1716

answers:

3

I am trying to write specs for a controller without using fixtures (instead employing mock models). This controller requires a user to be logged in, for which I'm employing AuthLogic, following the author's recommendations.

describe UsersController do

  def mock_user(stubs={})
    @mock_user ||= mock_model(User, stubs)
  end

  context 'when logged in' do
    before { activate_authlogic }

    it "exposes the logged-in user as @user in response to GET (show)" do
      UserSession.create(mock_user)
      ...
    end

    ...
  end

  ...
end

These examples all fail at the line UserSession.create(...), reporting to the effect of:

Mock 'User_1005' received unexpected message :changed? with (no args)

I'm not sure how to resolve this; is mocking with :changed? => false appropriate?

+1  A: 

Authlogic expects the record to acts like an active record instance. You can use a real instance or a mock, but if you use a mock/stub you must be sure it responds to all the methods required by Authlogic.

I would suggest to use a real active record object instead of a mock. If you don't want to use a fixture, you can use a Factory.

The last option would be to pass a mock that responds to any methods (you can easily accomplish this via method_missing). The problem with that solution is that you don't know in advance which value should return any specific method call.

Yes, you can pass false but this is not really a solution. It would require to manually try/add a default value until you find the mock object answering all the Authlogic request. But this would require you to constantly follow authlogic for any internal change to fix unanswered calls to your stub.

Simone Carletti
Does it happen, with session objects, that there aren't that many cases to cover?Here's what I worry about: Fixtures seem to work very well if you have an obvious set of cases to cover and especially if you can label them obviously, e.g. "admin_user" and "registered_user". As soon as they get hard to label, you start getting test code like "if quentin logs in, then you see X," which doesn't mean anything to me.Do sessions lend themselves well to fixtures?
Andres Jaan Tack
A: 

I found mocking authlogic objects were were hard and I ultimately gave up on mocking. Instead, I now use a generators approach using object daddy. My functional tests are much happier now. BTW, shouldaobject_daddy absolutely rocks. Shoulda's transactional contexts ensure that my test database remains clean and I do not have to mock simple activerecord objects in the first place.

Vagmi Mudumbai
+5  A: 

Iain posted a solution to using mock objects with AuthLogic. To rephrase, the following helpers go into spec_helpers.rb:

def current_user(stubs = {})
  @current_user ||= mock_model(User, stubs)
end

def user_session(stubs = {}, user_stubs = {})
  @current_user_session ||= mock_model(UserSession, {:user => current_user(user_stubs)}.merge(stubs))
end

def login(session_stubs = {}, user_stubs = {})
  UserSession.stub!(:find).and_return(user_session(session_stubs, user_stubs))
end

def logout
  @user_session = nil
end

I've incorporated this into my specs, and I find it does exactly what I was hoping. I have working controller specs that exploy mock models for the logged-in user, so now they don't all break when I add a field to User. Iain's example of implementing this in a spec is as:

describe SecretsController do
  before { login }
  it "should be very very secret!"
end

P.S. I hate to answer my own question, but this is the answer I was looking for; I just didn't find it early enough.

Andres Jaan Tack