Rspec user testing

So I’m building user accesses, working out how to use devise, rolify and Cancan.  And I’m trying to make sure I also learn how all this works with rspec for unit testing.  Which is a lot of stuff to do all at once!!
Anyway, I’ve decided that the right way to do this is to test your controller with a user of every type, and verify that the actions they can do are as you expect – security isn’t something that you should take chances on.  This was ending up with a lot of duplicate code, this post is about how I factored it out so that I had less duplication.

First, when you’re using rspec with devise, rolify and Cancan, you need to do some setup to get your rspec to even run.  I create login macros, the aim here is to give me a function “login_any_user” that takes in the role type that I’m trying to logon as, creates the user in the database, grants them that role, then logs in as that user.  I put this macro into spec/support/login.rb

  module LoginMacros
  def login_any_user (user=:user)
    case user
      when :system_admin  then login_system_admin
      when :account_admin then login_account_admin
      when :project_admin then login_project_admin
      when :user          then login_user
      when :no_access     then login_noaccess
      when :no_login      then no_login
      else                puts "user type:" + user + " not recognised in login.rb factory"

  def login_system_admin
    @request.env["devise.mapping"] = Devise.mappings[:system_admin]
    user = FactoryGirl.create(:system_admin)
    sign_in user

  def login_account_admin(:account_admin)
  .... for each of the roles that we know

Next, you need a file spec/support/devise.rb that has the following content, in order for rspec to play nice with devise

  RSpec.configure do |config|
    config.include Devise::TestHelpers, :type => :controller

In spec/spec_helper.rb add a line to also integrate with devise:

  config.include Devise::TestHelpers, :type => :controller

So, with that setup done, we can get on to the meat of the post, which is to create a controller test that executes against each of the users, grouping the test code for users that have similar behaviour on a given action. I decided that the tidiest way to do this is to organise the controller test in the following way:

  test index
    test users_with_behaviour_1
    test users_with_behaviour_2
  test show
    test users_with_behaviour_1
    test users_with_behaviour_2
    test users_with_behaviour_3

The rspec code looks something like this, testing the controller Cars:

require 'spec_helper'

describe CarsController do
  include Devise::TestHelpers
  include LoginMacros

  describe "GET index" do
    [:system_admin, :user_type_2, :user].each do |user|  
      it "retrieves all cars for user #{user}" do
        # Creates an account_status, then calls the list function, should find that one item in the list
        created_car = FactoryGirl.create(:car)

        get :index, {}

        # web response
        assigns(:car).should eq([created_car])
        response.code.should eq("200")
        response.should render_template("index")            

    [:no_access, :no_login].each do |user| 
      it "retrieves no cars, redirects user #{user} to homepage" do
        created_car = FactoryGirl.create(:car)

        get :index, {}

        # web response
        response.should redirect_to("/")

There is still a bit of thinking to go in here about how I label these user categories. I can imagine having a lot of similar controllers that have similar behaviour. When I add new user roles, it would be nice to not have to go to each controller test and update the roles on each test case. I’m thinking about externalising them into another helper. The main question here is whether there is enough commonality for that to make sense, or whether I’m better to live with a bit of maintenance load and leave the tests as they are – they’re very explicit the way they are.


Leave a Reply

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

You are commenting using your 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