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.