RSpec でリクエストを実行するときは let が便利
before ブロック
RSpec でコントローラや API のテストを作るとき、リクエストの実行を before ブロックに記述することがある。
describe FooController do before { post :create } context 'with some data' do before { create_data } it { expect(response).to have_http_status(:ok) } end end
create_data
は、リクエストに必要なデータを作る前処理で、リクエスト実行前に走ることを想定している。
しかし、このコードは想定通りの動きにならない。実際には、外側の before ブロックでリクエストが実行された後、内側のブロックで create_data
が実行される。これは、RSpec の仕様で、before ブロック は describe や context ブロックの外側から実行されるためだ。
before ブロックでリクエストを実行する方法は、気軽でよく使ってしまいがちだが、このようにリクエストの前に何らかの前処理を差し込みたい場合に不便である。
let ヘルパー
そこで、以下のようなリクエストを実行する let
を定義する。fetch_response
を呼び出すと、リクエストが走り、レスポンスオブジェクトを返す*1
# controller spec の `response` ヘルパーと重複するので、それっぽい別の名前をつけた let(:fetched_response) { post :create }
このようにリクエストの実行を let で定義しておけば、好きなタイミングでリクエストを実行できるようになるので、リクエストの前に前処理を差し込むが簡単になる。
describe FooController do context 'with some data' do before { create_data } it { expect(fetched_response).to have_http_status(:ok) } end end
好きなタイミングでリクエストを実行できるので、change マッチャーも簡単に使える。
it { expect { fetched_response }.to change(Post, :count).by(1) }
JSON API のテストならこんなふうに書くのが便利。JSON.parse
に symbolize_names: true
を渡せば、parse した Hash のキーをシンボルに自動で変換してくれる。
describe FooController do let(:fetched_response) { post :create } let(:parsed_response) do JSON.parse(fetched_response.body, symbolize_names: true) end context 'with invalid request' do before { create_data } it do expect(parsed_response).to match( type: 'validation_failed', errors: [ { field: 'xxx', code: 'xxxx' } ] ) end end end
こんな感じに、1 example に複数の expect を書いても、実行されるリクエストはただ一回。そう、let ならね。
describe FooController do let(:fetched_response) { post :create } context 'with invalid request' do before { create_data } it do expect(fetched_response).to have_http_status(:ok) expect(fetched_response.body).to eq({ status: 'ok' }.to_json) end end end