Testing your Application Controller with rSpec

Posted by Bart ten Brinke Wed, 23 Jan 2008 13:35:15 GMT

I was trying to create a function that would check the enforcement of the before filters in my application controller. After going through a lot of rspec documentation and examples, I found nothing that really suited my needs. After a long google, I found a mention in the rspec mailing list and a hint to a solution for this. This is a working example of this idea.

I have in my application controller the following code:

class ApplicationController < ActionController::Base
  before_filter :check_authorization
   ....
  # Checks if a user is authorized
  def check_authorization
    User.check_authorization(controller_name, action_name)
  end
end

And in spec/controllers/application/application_spec.rb the following description.

describe "an authorized controller", :shared => true do

  it "should have the check_authorization set in the before filter" do
    ApplicationController.before_filters.should \
      include(:check_authorization)
  end   
end

In all other controller specs I can now do this:

require 'application_controller_spec'

describe UnitsController, "in general" do
  it_should_behave_like "an authorized controller"
end

By doing this I now have a spec that ensures that every controller checks authorizations before doing anything else. How cool is that :)!

It would be nicer if the test handled skip before_filters in some nice way like: it_should_behave_like "a monkey".except_for_action('show'). Has anyone got any ideas for that?

Special thanks to Matthijs Langenberg for his insights.

Comments

  1. Matthijs Langenberg said about 6 hours later:
    Now I can finally remove those pesky pending messages like: "ApplicationController#set_cache_directory should be called on every request (Not Yet Implemented)" Though I don't like the idea of hardcoding all those action names, is that really the right behaviour we are describing? What about mocking ApplicationController.before_filter in some way? ApplicationController.should_receive(:before_filter).with(:check_authentication) What do you think?
  2. Bart ten Brinke said about 6 hours later:
    I totally agree that you want the spec to determine the actions in the controller dynamically. I just haven't found a good way for this. Also you want to skip actions in a controller that has a skip_before filter defined or be able to have a controller that should_behave_like "an authenticated controller".except_for_action(get). As for mocking the before filter: you can't do this. We are describing the behaviour of classes that inherit from application controller.Because ApplicationController is not really an available object in Rails, you are not able to define a spec like that: you cannot unit test an application controller.
  3. Andre Foeken said about 6 hours later:
    Your example is still a dirty adapted copy paste :) I think you don't need the controller or action name when you check the authentication of a user...
  4. Matthijs Langenberg said about 6 hours later:
    You'll want to seperate the behaviour of the actual before filter from the registration of that method as a before filter. You don't need to test that the method actually gets called, that's part of the Rails framework and needs to be tested by Rails' tests. I think I've found a solution that's a little more elegant than yours: ApplicationController.before_filters.include?(:check_authentication).should be_true This ensures that the 'check_authentication' method is actually registerd as a before filter, instead of checking if the method has been called. So you can't 'trick' the example by calling the method manually from the action (or even by using an after_filter!) See http://pastie.caboo.se/142517 for a detailed example.
  5. Bart ten Brinke said about 6 hours later:
    Andre Foeken: Yes, you do. As you want to know if this user is authorized to perform that action on that controller. Matthijs Langenberg: You have a good point there, I'll adapt the article.
  6. Andre Foeken said about 7 hours later:
    Then change it to *authorized* instead of *authenticated* ;)! By the way: ApplicationController.before_filters.include?(:set_cache_directory).should be_true can be nicer: ApplicationController.before_filters.should_include(:set_cache_directory)
  7. Bart ten Brinke said about 7 hours later:
    Wrapped all the comments up in the article. Enjoy :)
  8. max williams said 30 days later:
    Hi Bart - thanks for this, very nice! I put the methods in spec_helper instead of application_controller_spec, since i have a few and i didn't want to use them all in application_controller (i have controllers that have more filters than application_controller). This feels cleaner to me since spec_helper is already required by all the spec files. The only change required was to call 'before_filters' on "controller.class" instead of ApplicationController - here's one of mine as an example - describe "a controller requiring an admin user", :shared => true do it "should have admin_user? set in the before filter" do controller.class.before_filters.should include(:admin_user?) end end What do you think of them living in spec_helper - is that appropriate? I'm qute new to coding so am never sure about these sorts of design issues :)
  9. max williams said 30 days later:
    Sorry about the broken code formatting btw - i should have previewed.
  10. Pat Maddox said 31 days later:
    One thing that seems to be missing is a specification for the actual behavior of the check_authorization filter. What happens when it passes? What happens when it fails? What does it take to make it pass or fail?
  11. Bart ten Brinke said 43 days later:
    That's true. In our case this just calls a class function, which is tested in the spec of that class.
  12. Fester said 62 days later:
    Recently i've wrote a simple matched to test filter presence in a more natural way. You can check it out: http://github.com/fester/have_filter/tree/master
  13. Alex said 158 days later:
    Good post, but has anyone figured out how to do this with more fine-grained precision? Instead of just checking if a specific controller has a certain filter, it would nice to be able to this on a per-action level. Any suggestions?

(leave url/email »)

   Preview comment