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

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

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
    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] =
    #p "set request.session[:user_id] to #{request.session[:user_id]}"

  def create_user name = "tester"
    user =
    user.login = name = "#{name}"
    user.username = name.titlecase
    user.password = "12345678"
    user.password_confirmation = "12345678" => false)
    UserLog::User.find_by_login name

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

See part 2 here.

No comments: