Testing Policies
Authorization is critical to your application’s security. Policies should have thorough test coverage - this is one area where 100% coverage makes sense!
Why Test Policies?
- Security critical - Bugs in authorization can lead to data breaches
- Easy to test - Policies are plain Ruby classes, no HTTP needed
- Fast - No database or network in most tests
- Documentation - Tests document expected behavior
Basic Policy Testing
Policies are regular Ruby classes. Test them like any other class:
require "test_helper"
class ProductPolicyTest < ActiveSupport::TestCase def setup @product = products(:one) @admin = users(:admin) @user = users(:regular) end
test "anyone can view products" do policy = ProductPolicy.new(@product, user: nil) assert policy.apply(:index?) assert policy.apply(:show?) end
test "logged in users can create products" do policy = ProductPolicy.new(@product, user: @user) assert policy.apply(:create?) end
test "guests cannot create products" do policy = ProductPolicy.new(@product, user: nil) refute policy.apply(:create?) end
test "admins can do anything" do policy = ProductPolicy.new(@product, user: @admin) assert policy.apply(:create?) assert policy.apply(:update?) assert policy.apply(:destroy?) endendCreate Test Fixtures
First, create fixtures for testing. Open :
admin: email_address: admin@example.com password_digest: <%= BCrypt::Password.create('secret123') %>
regular: email_address: user@example.com password_digest: <%= BCrypt::Password.create('secret123') %>Open :
one: name: T-Shirt published: true
two: name: Draft Product published: falseWrite the Policy Tests
Open :
require "test_helper"
class ProductPolicyTest < ActiveSupport::TestCase setup do @product = products(:one) @admin = users(:admin) @user = users(:regular) end
# Index tests test "anyone can view product list" do policy = ProductPolicy.new(Product.all, user: nil) assert policy.apply(:index?) end
# Show tests test "anyone can view a product" do policy = ProductPolicy.new(@product, user: nil) assert policy.apply(:show?) end
# Create tests test "guests cannot create products" do policy = ProductPolicy.new(Product.new, user: nil) refute policy.apply(:create?) end
test "logged in users can create products" do policy = ProductPolicy.new(Product.new, user: @user) assert policy.apply(:create?) end
# Update tests test "guests cannot update products" do policy = ProductPolicy.new(@product, user: nil) refute policy.apply(:update?) end
test "logged in users can update products" do policy = ProductPolicy.new(@product, user: @user) assert policy.apply(:update?) end
# Admin tests test "admins can do everything" do policy = ProductPolicy.new(@product, user: @admin) assert policy.apply(:index?) assert policy.apply(:show?) assert policy.apply(:create?) assert policy.apply(:update?) assert policy.apply(:destroy?) end
# Alias tests test "new is aliased to create" do guest_policy = ProductPolicy.new(Product.new, user: nil) user_policy = ProductPolicy.new(Product.new, user: @user)
refute guest_policy.apply(:new?) assert user_policy.apply(:new?) end
test "edit is aliased to update" do guest_policy = ProductPolicy.new(@product, user: nil) user_policy = ProductPolicy.new(@product, user: @user)
refute guest_policy.apply(:edit?) assert user_policy.apply(:edit?) endendRun the Tests
$ bin/rails test test/policies/You should see:
Running 10 tests in a single process (parallelization threshold is 50)Run options: --seed 12345
# Running:
..........
Finished in 0.123456s, 81.0001 runs/s, 81.0001 assertions/s.
10 runs, 10 assertions, 0 failures, 0 errors, 0 skipsTesting Scopes
Test scopes separately:
test "admins see all products" do policy = ProductPolicy.new(nil, user: @admin) scope = policy.apply_scope(Product.all, type: :relation)
assert_includes scope, products(:one) assert_includes scope, products(:two)end
test "regular users only see published products" do policy = ProductPolicy.new(nil, user: @user) scope = policy.apply_scope(Product.all, type: :relation)
assert_includes scope, products(:one) # published refute_includes scope, products(:two) # draftend
test "guests only see published products" do policy = ProductPolicy.new(nil, user: nil) scope = policy.apply_scope(Product.all, type: :relation)
assert_includes scope, products(:one) refute_includes scope, products(:two)endTesting Failure Reasons
Test that the correct reasons are provided:
test "guest create returns not_logged_in reason" do policy = ProductPolicy.new(Product.new, user: nil) policy.apply(:create?)
assert_equal :not_logged_in, policy.result.all_details[:reason]endNext, let’s learn how to test authorization in controllers.
Files
Preparing Environment
- Preparing Ruby runtime
- Prepare development database