Testing a rails before_filter method

I finally figured out a kind of cool way in Ruby using Rspec to test a controller’s before_filter method a few days ago.

While trying to figure this out, I came across all kinds of posts saying why you shouldn’t test the before_filter because it means looking too closely at the implementation by doing that.  (I disagree; I think this, like any other method, can and should be unit tested.)

I came across other posts that told how to skip the before_filter altogether and test everything else. (Valuable in some cases I’m sure, but not what I was trying to do.)

And I came across many other posts that had examples that did not work for me (perhaps different versions of rspec/ruby/rails or any number of things).

I came across some posts said to test the functionality of the before_filter method in one of the subclasses of ApplicationController.  I could do that, but then I have to pick a subclass at random.  And since the method I’m trying to test is defined ON ApplicationController, I feel like it is more intuitive to test that IN application_controller_spec.rb, not some other, random test.  And if the randomly chosen controller happened to ever get removed from the app in the future, the spec for that would also get removed, and thus inadvertently removing any coverage of the before_filter method in app controller.

I found out that Rspec has a cool way of defining anonymous controllers.  And this worked out well for testing ApplicationController.  So I wanted to share how I used anonymous controllers, as well as some things I tried along the way that did NOT work (almost, but not quite).

(These examples use Rails 3.1.1 and Rspec 2.12.0)

The application code:

application_controller.rb

# The before_filter method "check_permission" is defined here.  It is
# not specified as a before_filter in ApplicationController because
# some controllers in the app will want to use it, and some will not.
1  class ApplicationController
2    def check_permission
3      @current_user = session[:user]
4
5      #Check the permissions of the logged in user in the session
6      if @current_user does not have permission #pseudocode
7        render( :file => File.join(Rails.root, 'public/403.html'),
8                :status => 403,
9                :layout => false )
10     #else don’t do anything. The app will continue as usual
11   end
12 end

members_controller.rb

# There are rules around who can view members.
# Different logged in users can view different members.
1 class MembersController < ApplicationController
2   before_filter :check_permission
3
4   def show
5     member_id = params[:id]
6     # get the details for the given member id
7     # etc....
8   end
9 end

The test code:

I came across some suggestions that said to just write a test that calls the method directly.  But in this case, the before_filter method uses the HTTP session.  If you call a method outside of the context of an  HTTP get/post, you don’t have access to HTTP things like session, request or response.  So calling the method directly did not work:

application_controller_spec.rb

 1  require 'spec_helper'
 2
 3  describe ApplicationController do
 4    before do
 5      # Put a user in the session
 6      # do any other rspec mocking/stubbing necessary
 7    end
 8
 9    it 'should allow viewing of users if the user has permission' do
 10     ApplicationController.new.check_permission
 11     # then test stuff here ...
 12   end
 13 end

Running this gave the following error on the line 11.

RuntimeError:
 ActionController::Metal#session delegated to @_request.session, but @_request is nil: 
 #<ApplicationController:0x007fbac2aed3f8 @_routes=nil, @_action_has_layout=true, 
 @_view_context_class=nil, @_headers={"Content-Type"=>"text/html"}, @_status=200, 
 @_request=nil, @_response=nil>

I tried defining my own anonymous controller like this:

application_controller_spec.rb

 1  require 'spec_helper'
 2
 3  class AnonymousController < ApplicationController
 4    before_filter :check_permission
 5    def show
 6      render :text => 'Hello'
 7    end
 8  end
 9
 10 describe AnonymousController do
 11   before do
 12     # Put a user in the session
 13     # Any other rspec mocking/stubbing that needs done
 14   end
 15
 16   it 'should allow viewing of users if the user has permission' do
 17     get :show , :id => 123456
 18     response.code.should eq "200"
 19     response.body.should include "Hello"
 20   end
 21 end

And got this error on line 17:

ActionController::RoutingError:
 No route matches {:id=>"111111", :controller=>"anonymous", :action=>"show"}

But there’s a more Rspec-y way to do anonymous controllers.  And it magically sets up the routes for you to avoid the error above.

application_controller_spec.rb

 1  require 'spec_helper'
 2
 3  describe ApplicationController do
 4
 5    before do
 6      # put a user in the session
 7      # do any other rspec mocking/stubbing necessary
 8    end
 9
 10   # This is the anonymous controller:
 11   controller do
 12     before_filter :check_permission
 13
 14     def show
 15       render :text => "Hello"
 16     end
 17   end
 18
 19   it 'should allow viewing of users if the user has permission' do
 20     get :show , :id => 123456
 21     response.code.should eq "200"
 22     response.body.should include "Hello"
 23   end
 24
 25   it 'should not allow viewing of users if the user does not have permission' do
 26     get :show , :id => 111111
 27     response.code.should eq "403"
 28   end
 29 end
About these ads
Posted in code. Tags: , . 2 Comments »

2 Responses to “Testing a rails before_filter method”

  1. Lucas Pottersky Says:

    Wondering how to achieve the same goal using Test::Unit… The solutions posted on http://stackoverflow.com/questions/251225/how-to-test-controller-filters-in-ruby-on-rails-and-testunit led me to the “request is nil” error.

  2. Kamil Bednarz Says:

    Thanks for this tip! It saved my specs and allowed keeping them nice and clean! :)


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: