[rspec-users] Stubbing controller methods vs model methods

Ben Mabey 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 []
> 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 :)

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 

Hope that helps,


1. http://www.mail-archive.com/rspec-users@rubyforge.org/msg03128.html

More information about the rspec-users mailing list