RSpecを書く機会が増えてきたので、記法の備忘録として記事にします。
describe ~ it
テストしたいアクションをdescribeで囲みます。
controllerのupdateアクションのspecを書く場合
describe ‘#update’ のように、アクション単位でテストを書いていきます。
# some_logics_controller_spec.rb
describe 'アクション名' do
# テストしたいアクションをここに定義する
subject { get :index }
it 'indexテンプレートで描画されること' do
# subjectを呼び出すと、indexアクションが実行される
subject
expect(response).to render_template('index')
end
end
let、let!
リクエストを送る際に必要なパラメータや
テスト中に変数を使いたい場合は、let を使用します。
let は遅延評価され、let! は即時評価されます。
つまり、subjectを実行する前にアクセスする必要がある変数は
let! で定義しておく必要があります。
例えば以下の例では
letで定義しているuser、parametersは
it ‘how_test_context’ブロック内で、subjectが実行された時に評価されます。
# some_logics_controller_spec.rb
describe 'アクション名' do
subject { patch :update, params: parameters }
# let(:variable_name) で変数を定義できる
## let は変数が呼ばれたタイミングで評価される
let(:user) { create(:user) }
let(:parameters) { { user_id: user.id, name: 'ミミッキュ' } }
## let! はsubjectが実行される前に評価される
let!(:item) { create(:item) }
## user_itemはsubjectの後に評価されるので、エラーになる
let(:user_item) { create(:user_item, user_id: user.id, item_id: item.id) }
it 'userがitemを持っていること' do
subject
expect(user.user_items.first).to eq user_item
end
end
対して、userとitemを紐づけるためのuser_itemは
letで定義していますが、テスト内で呼び出されるのはsubjectが実行された後なので、このspecはエラーとなってしまいます。
context ‘some_test’ do
シンプルなテストであれば、describeとitだけで事足りますが
より複雑な条件でテストを書きたい時もあります。というか、ほとんどがそうだと思います。
そういう時は、context ‘some_pattern’ do で囲います。
次の例では、無効なユーザーのテストをcontextで囲み
before doで、テストが実行される前にuserのステータスをinactiveに変更しています。
(before doについては、次の節で触れていきます)
# some_logics_controller_spec.rb
describe 'アクション名' do
subject { post :login, params: { user: user } }
let(:user) { create(:user, status: :active) }
# 正常系の処理
it 'ログインできること' do
subject
expect(response.status).to eq 201
end
# context 'pattern_name' do で、特定の条件下での処理を囲んであげる
context '無効なユーザーの場合' do
before do
user.inactive!
end
it 'ログインできないこと' do
subject
expect(response.status).to eq 403
end
end
end
ログイン機能のテストを書く時など
テストを書き分けたい場合に便利です。
before_action
railsでは、特定のアクションを呼び出す前に実行する処理(before_action)を定義できます。
rspecでも同じようなことができて
before do ブロックで処理を囲んであげることで、ブロック内の処理を
itが実行される直前に実行してくれます。
先ほどのログイン機能を例に、以下のコードを見てみます。
あらかじめ無効なユーザーをlet変数で定義しておき、
beforeアクションでユーザーをアクティベートさせてから、subject(ログイン)を実行しています。
# some_logics_controller_spec.rb
describe 'アクション名' do
subject { post :login, params: { user: user } }
# 無効なユーザー
let(:user) { create(:user, status: :inactive) }
# before do で囲んだ処理は、itの直前に毎回実行される
before do
user.active!
end
# before doの処理が実行される
it 'ログインできること' do
subject
expect(response.status).to eq 201
end
# before doの処理が実行される
it 'ログインできないこと' do
subject
expect(response.status).to eq 403
end
end
しかし、beforeアクションは、itの直前に毎回実行されるので
it ‘ログインできないこと’ do の部分でエラーになります。
before(:context)
beforeブロックは、デフォルトでは itが実行される直前に毎回呼ばれます。
先ほどの例で言えば
it ‘ログインできないこと’ do の直前にbeforeが実行される時
userはすでにアクティベーションされている状態になります。
(it ‘ログインできること’ do の直前にもbeforeが実行されるため)
itの前に、一度だけ実行すれば良い場合は
before(:context) を使用します。
before(:context) は、describeや、contextのブロックごとに毎回実行されます。
# some_logics_controller_spec.rb
describe 'アクション名' do
subject { post :login, params: { user: user } }
# 無効なユーザー
let(:user) { create(:user, status: :inactive) }
# before(:context) はdescribe、contextのグループごとに実行される
before(:context) do
user.active!
end
it 'ログインできること' do
subject
expect(response.status).to eq 201
end
it 'ログインできないこと' do
subject
expect(response.status).to eq 403
end
end
before(:context)は、before(:all)のエイリアスです。
何も指定しない場合、beforeのデフォルトは、before(:each)になり
before(:each)は、itの直前に毎回実行されます。