Stubbing controller methods vs model methods

I had an error that I couldn’t figure out, then when writing up a
question for the forum I figured it out. The thing is I don’t
understand why the change that was made works and why what existed
before didn’t.

Here is the initial post when I had the error:

In the controllers/application.rb file

I have a method that finds the account based on the subdomain. This
method is stubbed out for the tests.
def find_account
@account = Account.for(request.host.split(“.”).first) #TODO: find
out why request.subdomains returns []
end

In my controller tests I would like to stub this method. So I created a
method within the authenticated_test_helper.rb (I am using RESTful
Authentication plugin)

def login_with_account(account) # <===
# User
current_user = mock_model(User)
controller.stub!(:current_user).and_return(current_user)

# User's account
account.stub!(:users).and_return([current_user])
current_user.stub!(:account).and_return(account)

# Application methods
controller.stub!(:logged_in?).and_return(true)
controller.stub!(:authorized?).and_return(true)
controller.stub!(:find_account).and_return(account)  # <===

find_account stubbed out
end

Now in the before methods of my controller specs I have:
before :each do
account = mock_model(Account, :subdomain => “test2”)
login_with_account(account) # <===
end

The place that keeps throwing up is the controller’s index method

def index
@users = @account.users
end

where the error message is:
NoMethodError in ‘Admin::UsersController GET, test2.localhost/users
should validate the user’
You have a nil object when you didn’t expect it!
The error occurred while evaluating nil.users

*** The fix
I found that if I stubbed out the Account.for method instead of the
controller helper method it then worked.
+>> Account.stub!(:for).and_return(account)
->> controller.stub!(:find_account).and_return(account) # removed

As a reminder here is the find_account method
def find_account
@account = Account.for(request.host.split(“.”).first)
end

Am I not stubbing the controller methods properly? That method is in
the ApplicationController so it would be inherited and therefore
accessible through the “controller” reference right?

Thanks for the help.

BTW has any heard about the Pragmatic Rspec book? I swear I check the
pragprog.com site everyday for that book :slight_smile:

Chris O. wrote:

method is stubbed out for the tests.
# User
controller.stub!(:find_account).and_return(account) # <===

*** The fix
Am I not stubbing the controller methods properly? That method is in
the ApplicationController so it would be inherited and therefore
accessible through the “controller” reference right?

Thanks for the help.

BTW has any heard about the Pragmatic Rspec book? I swear I check the
pragprog.com site everyday for that book :slight_smile:

Hey Chris,
The problem is that you stubbed a method with side effects. Namely,
your find_account method not only returned the found account but it set
the instance variable @account. I’m guessing that this is a filter you
had before your index action. So in your first attempt when you stubbed
find_account you had it return the account but because it was stubbed
the actual instance variable that the action relies on was never set!
The way you did it the second time is correct because that allows your
find_account method to actually run and set the @account variable. This
is actually the much preferred way because you are stubbing out calls on
external objects and not internal methods on the class you are specing.
You can then follow up with an expectation using mocking to assure that
the correct call to Account is being made in find_account.

In a previous thread we were talking about the evils of stubbing/mocking
methods on the object that your testing. That said I think most every
rspec_on_railer stubs out the current_user methods on there controller
when they are testing. As David pointed out though, that doesn’t make
it right and you should violate that rule consciously knowing that you
are doing something wrong. I think the problem that you faced here is a
great example of why stubbing an objects own method calls can be
dangerous and is a TDD no no. If you have a lot of logic like this in
your controller and to make it easier to test you could move all of the
authentication code into it’s own object. In the same thread Zach
Dennis actually posted[1] some of the code they are using that has the
authentication logic in another object that allows more easier and
better testing. The more and more I think about this the more it makes
sense, the controller really shouldn’t be burdened with all of that
logic…

Hope that helps,

Ben

  1. http://www.mail-archive.com/[email protected]/msg03128.html

Hey Ben,

That makes perfect sense. Thanks for pointing out the error because I
don’t think I would’ve been able to figure it out.

Thanks, as well, for the stubbing/mocking tip. I will keep my eyes open
for that in the future.