RSpec testing all actions of a controller
Posted by Jim Morris on Sat Jul 28 14:23:07 -0700 2007
A pattern I find very helpful is to find all the actions in a controller and apply a test to all those actions.
For instance this is useful for automatically testing all actions are protected from unauthorized access when using a login system.
One nice feature of this pattern is that if you add an action to a controller it will automatically be tested. This is less helpful if you use
before_filter :login_required, :except => {...}
as it will automatically be protected, but there are other use cases where this is not the situation. Just as in the except clause above you need to explicitly add any action that does not need to be tested to an exception list, which is supported by this pattern.
Here are the methods I use to test for login accessibility.
I put this in my spec_helper.rb and include it as shown here:
The get_all_actions
method collects all the public un-inherited
methods in the given controller, these will consist of all the
accessible actions in that controller. I explicitly exclude
rescue_action
as it is created by RSpec itself and should not be
tested. Note it will not see any actions that are in application.rb so
you need to add those to the list manually of you want them tested.
(See the :include option in the example).
The controller_actions_should_fail_if_not_logged_in
could be put in
the spec itself rather than the spec_helper, but as I call this from
all my controller specs it is more DRY to put it here. This method
takes the controller name and an option array of actions names to
ignore. This method tests all the actions and makes sure I get the
expected result of the filter failing due to not being logged in.
I show an example spec that uses this to test my events controller, it mocks the login calls to say I am not logged in, and then tests them with the exceptions of the actions in this controller that do not require one to be logged in.
This pattern can be extended to test all sorts of things, and is especially useful for testing things where you can add an action and forget to do something in a filter to protect it. Make sure the default is on the side of caution though. IE you need to explicitly except actions rather than include actions.
Another example is something I recently stumbled upon in my RESTful controllers. In many cases it is good to use a verify statement to make sure that the RESTful actions actually can only be called with PUT, POST or DELETE and fail if called with GET. I use this statement in my controllers to enforce this...
I test this in my specs using this in the MySpecHelper Module
and an example of its use in a spec...
Now whenever I add an action, the default is that it will fail with a GET, unless I add it to the exclude list in the spec, this will remind me to check if the action required PUT, POST or DELETE instead and to add it to the verify if so or add it to the specs exclude list if not.
These automatic tests keep me honest, especially in the last case where you really don't want a GET to be able to delete something.
I hope this pattern is useful to you.
Of course this pattern can also be applied to regular rails tests as well as RSpec, and is particularly nice when used in integration tests.
Interesting, that's a very smart way of quickly writing specs to verify that you setup your authentication properly. I'm just wondering when you would use the :include method? It seems to me that you might want to test all the actions except few. When do you need to include an action?
Matt
I use the :include option because I have an action in application.rb which can only be called when logged in so needs to be tested, but it will not be found by `get_all_actions` because that does not find inherited methods from the base class. So include is there to add those actions.
Thanks for this, it's really useful!
I've got one problem with it though. I run into a bunch of 'No route match exceptions' since it tries to call my restful contollers with GET when they are only accessible through the other operations (because of the way the routes are setup in routes.rb).
That shouldn't be too hard to fix, just create a map of actions to method, and call post, put or delete where appropriate with a default of get, which should throw an error if you add a new action.
Another approach would be to test whatever is in your routes maps with the appropriate method.
I tried your second approach, ie checking the routes maps but I ran into trouble with actions that route like this:
`POST /users/:id/add_friend/ {:controller=>"users", :action=>"add_friend"}`
The routing greps for the id and if it isn't supplied, it won't route the request. I guess I'll have to go with your first approach.
Hi can i have a sample rspec examples which depicts how to test one controller file.
The rspec given above is a bit complex i felt as a beginner.
Thanks in advance
To see examples of normal RSpec usage for a controller goto their web page, they have plenty of examples under the Rails section. This Blog is about testing all actions in a given controller,which is an advanced topic, and not everyone will need to do this.
i use this for having all the actions of my controller:
` (Admin::UsersController.public_instance_methods - ApplicationController.public_instance_methods).each do |action|....`
I think is very simple, anyway your solution is cool
This is pretty fantastically sweet
I did however have to change the `get_all_actions` method to use camelize rather than capitalize so that my multiword controllers would work.
i.e:
`c= Module.const_get(cont.to_s.pluralize.capitalize + "Controller")`
becomes
`c= Module.const_get(cont.to_s.pluralize.pluralize + "Controller")`
So I can call the method with 'user_account' and it finds the appropriate UserAccountController
Does anyone have a example put that works on their controller? I can't get any updates to work using put. Get, and Post work fine. I also can't find any examples online that actually have the code that does it. This example uses puts .... and assumes that it works...
I get a nil.downcase with no idea where/how to find it. The actually code manually tests fine and the services works from XML or the screens using html. Any put examples out there?
how could i test these codes?
`before_filter :login_required, :only => [:show, :edit, :update]
skip_before_filter :store_location_filter
after_filter(:create_referral_point, :only => :activate)`
@Brian, having the same issues. I continue to get a nil.downcase repeatedly and don't have a clue where it's at. Testing good, still no solutions.
In newer Rails/rspec versions (Rails 3, rspec 2.5) you can replace get_all_actions call with controller_class.action_methods