Chris Olsen
2008-Jan-30 01:23 UTC
[rspec-users] 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 :) -- Posted via http://www.ruby-forum.com/.
Ben Mabey
2008-Jan-30 04:28 UTC
[rspec-users] Stubbing controller methods vs model methods
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 logic... Hope that helps, Ben 1. http://www.mail-archive.com/rspec-users at rubyforge.org/msg03128.html
Chris Olsen
2008-Jan-30 06:13 UTC
[rspec-users] Stubbing controller methods vs model methods
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. -- Posted via http://www.ruby-forum.com/.