Shoulda Macros for Testing (Not) Logged-In Filters

In any Rails app with a user system, certain actions are likely to have a before_filter to ensure that a user is either logged in or not logged in before accessing those actions. For instance, the edit profile page should only be accessible if a user is logged in. The registration page, conversely, should only be accessible if a user is NOT logged in.

With Shoulda, we can easily create a few macros which make testing this behavior as simple as adding a single line to the test context for each action that uses one of these filters.

# test_helper.rb

# ...

class ActiveSupport::TestCase

  include RR::Adapters::TestUnit

  def self.should_require_user(opts = {})
    simulate_no_user = lambda do
      stub(@controller).current_user { nil }
      @preserve_login = true
    end
    
    should "require user", :before => simulate_no_user do
      assert_contains flash.values, (opts[:flash] || /Please log in/)
      assert_redirected_to login_path
    end
  end

  def self.should_require_no_user(opts = {})
    simulate_user = lambda do
      stub(@controller).current_user { opts[:user] || Factory(:user) }
      @preserve_login = true
    end
    
    should "require no user", :before => simulate_user do
      assert_contains flash.values, (opts[:flash] || /Please log out/)
      assert_redirected_to root_path
    end
  end
  
  # ...

end

def login(user)
  stub(@controller).current_user { user } unless @preserve_login
end

By adding one of these macros to a Shoulda context it will, before that context's setup block, simulate a user being logged in (or out), execute the setup block (preserving the state of @controller.current_user), and then ensure that the appropriate redirect occurs and flash message is set.

So, with these macros defined, our users_controller test looks like this:

# users_controller_test.rb

require 'test_helper'

class UsersControllerTest < ActionController::TestCase
  
  context 'get new' do
    
    setup { get :new }
    
    should_require_no_user
    # ...
    
  end
  
  context 'get edit' do
    
    setup do
      login Factory(:user)
      get :edit
    end
    
    should_require_user
    # ...
    
  end
  
  # ...
  
end

The resulting tests are succinct, explicit and easy-to-type.

You can recycle this pattern for other before_filters in your app too. For instance, if you have admin-only controllers / actions, just add macros that simulate an admin instead of a regular user and you're ready to test.