[rspec-users] Stubbing controller methods vs model methods
ben at benmabey.com
Tue Jan 29 23:28:15 EST 2008
Chris Olsen wrote:
> 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 
> 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)
> # User's account
> # Application methods
> controller.stub!(:find_account).and_return(account) # <===
> find_account stubbed out
> Now in the before methods of my controller specs I have:
> before :each do
> account = mock_model(Account, :subdomain => "test2")
> login_with_account(account) # <===
> The place that keeps throwing up is the controller's index method
> def index
> @users = @account.users
> 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)
> 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 :)
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 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
Hope that helps,
More information about the rspec-users