Friday 31 May 2013

Testing Protected Methods in a Controller, Part 1

Methods in a controller (including applicatin_controller.rb) can be divided between those that are actions (so invoked by HTTP requests) and those that are not. Those that are not should all be set as protected (or private), and it is these that I am posting about.

The first thing to do is to shift as much as you can out of the controllers. If at all possible, put in in a model or a library, and then just unit test them. However, that is not always easy, say because you want to invoke a redirect from the method or you need quick access to the session (I expect you could handle the session in a library file, but I think it would be messy and more trouble than it is worth).

So we have a bunch of protected methods, and they all need testing.

While you could test them in the same file you test your actions, thematically they are very different, and I think it makes more sense to put them in their own file. Here is an example. The only tricky part is that you need to say what controller it should use with the "tests" command. Oh, and you need to use "send" to access the method being tested, as it is protected.

require 'test/test_helper'

class ControllerMethodsTest < ActionController::TestCase
  tests PostsController
  
  def setup
    user_setup
  end

  def test_current_user
    login_as ['trainer', 'it'], request
    assert_equal 'tester', @controller.send(:current_user).login
  end
end

The "user_setup" methods puts some roles into the testing database. The "login_as" method is one that I have used a lot for testing actions relating to web pages that have the user logged in.

  def login_as roles, request
    create_user
    user = UserLog::User.find_by_login 'tester'
    roles = [roles] unless roles.is_a? Array
    roles.each { |role| assign_role user, role }
    request.session[:user_id] = user.id
    #p "set request.session[:user_id] to #{request.session[:user_id]}"
  end

  def create_user name = "tester"
    user = UserLog::User.new
    user.login = name
    user.email = "#{name}@domain.com"
    user.username = name.titlecase
    user.password = "12345678"
    user.password_confirmation = "12345678"
    user.save(:validate => false)
    user.send(:activate!)
    UserLog::User.find_by_login name
  end

  def assign_role(user, rolename)
    role = UserLog::Role.find_by_rolename(rolename)
    raise "Failed to find role #{rolename}" if role.nil?
    permission = UserLog::Permission.new
    permission.role = role
    permission.user = user
    permission.save(:validate => false)
  end

See part 2 here.

No comments: