cancancanを使った際、テストに学びがあったので投稿します。
前提:cancancanとは
権限による制御が簡単にできるgemです。
- cancancan
-
Rails|CanCanCanの使い方解説
↓私も1つ記事を書きました。 - cancancanでモデル名に紐づかない制御をする方法
cancancanのテスト
requestスペックなどで特定の権限が各アクションを実行できるかどうかをテストする方法もあると思いますが、今回はcancancanが用意しているmatcherを使ってテストする方法を紹介します。
require "cancan/matchers"
describe "User" do
describe "abilities" do
subject(:ability) { Ability.new(user) }
let(:user) { nil }
context "when is an account manager" do
let(:user) { create(:account_manager) }
it { is_expected.to be_able_to(:manage, Account.new) }
end
end
end
コード例は、上記ドキュメントに書いてあるままです。
ポイントは、cancan/matchers
をrequireすることでbe_able_to
というmatcherを使えるようになるというところだと思います。
注意点
複数の権限をテストするときは注意が必要です。
例えば、下記のようなかたちで権限を設定しているとします。
def initialize(user)
can :read, Profile
can :read, Job
cannot :read, Account
end
この時、RSpecを以下のようなかたちにすると、テストが通ります。
let(:user) { create(:user) }
it { is_expected.not_to be_able_to(:read, [Profile.new, Account.new]) } # => テスト通る
この結果は、私の直感に反していました。
Profileにはcan: readを設定しているので、テストは失敗するのではないかと思ったからです。
一方で、以下のテストは失敗します。
let(:user) { create(:user) }
it { is_expected.to be_able_to(:read, [Profile.new, Job.new]) } # => テスト通らない
どちらもreadにはcanを設定しているのですが、失敗してしまいます。
どうやらbe_able_to
の第2引数には配列を渡してもまとめてチェックしてくれるわけではないみたいです。
(権限設定側では、can :read, [Profile, Job]
のかたちでまとめて権限付与できます)
したがって、最適解かどうかはわかりませんが、aggregate_failures
などを使って1行ずつ書いていくといった方法が良いのかなと思いました。
let(:user) { create(:user) }
it 'テスト内容' do
aggregate_failures do
is_expected.to be_able_to(:read, Profile.new)
is_expected.to be_able_to(:read, Job.new)
end
end
ちなみに、be_able_to
の第1引数はまとめて記載できます。
def initialize(user)
can [:create, :read], Profile
end
上記の設定をしているとき、以下のテストはコメントに書いてある通りの結果になるので、第1引数は配列のかたちでまとめて書いてしまって問題なさそうです。
let(:user) { create(:user) }
it { is_expected.to be_able_to([:create, :read], Profile.new) } # => テスト通る
it { is_expected.to be_able_to([:create, :update], Profile.new) } # => テスト通らない(updateはcanの設定をしていないので期待通り)
間違いなどあったらご指摘ください。