読者です 読者をやめる 読者になる 読者になる

プログラミング 美徳の不幸

Ruby, Rails, JavaScriptなどのプログラミングまとめ、解説、備忘録。

controller specあれこれ

Railsのcontrollerをテストする際にrspecを使ってるんだけど、rspec業界的に最近shouldよりもexpectを使おうという風潮らしい。

@product.price.should eq 2000   #=>いけてない
expect(@product.price).to eq 2000 #=> いけてる

ただ、expectはitブロックの中でしか使えないのでRailsのcontroller specでいままでこう書けてたやつをexpectで置き換えようとするとダルい。

before { post :create }
it { should redirect_to root_path }

しかも面倒なのはexpectのうちブロックを引数にとる書き方はこれと併用しようとするとbeforeの中でpostしちゃダメ(下記参照)なのでネストが変になる。

it do
  expect { post :create }.to change(Product, :count).by(1)
end

context do
  subject { response }
  before { post :create }
  it { should redirect_to root_path }
end

で、これに対するひとまずの答えはこのpost :createをletで変数にしちゃう。

let(:create_product) { post :create, product: product_attributes }
context 'with valid attributes' do
  let(:product_attributes) { 妥当なパラメータ }
  it { expect(create_product).to redirect_to root_path }
  it { expect { create_product }.to change(Product, :count).by(1) }
end

context 'with invalid attributes' do
  let(:product_attributes) { ダメなパラメータ }
  it { expect(create_product).to render_template('new') }
  it { expect { create_product }.not_to change(Product, :count) }
end

expect()の場合は()の中が主語になる。一方でexpect{}の場合はblockの中で実行された内容によって発生する事象について調べるのに使う。自分の場合だいたいchangeかraise_errorをよく使う。(てかこれしかない気がする)
で、create_product自体はresponseなのでredirect_toとかrender_templateとかが使える。

まぁひとまずこれでredirect_toとchangeとかを併用しても違和感なくはできたけど、今度はresponse.bodyとかのチェックを混ぜるのどうすんのって話になる。個人的に上記のコードは主に更新系のアクション、要するにcreate, update, destroyに使うので、だいたいindexとかshowとかにredirectされるわけで、response.bodyをチェックするテストを上記のようなテストと一緒に書きたいと思うことはあんまないけど、たぶんその場合はおとなしくdescribeで囲っておくのが一番言い気がする。

let(:create_product) { post :create }
it { expect(create_product).to redirect_to root_path }
it { expect { create_product }.to change(Product, :count).by(1) }

describe 'view' do
  render_views
  subject { create_product }
  its(:body) { should have_css('.product, count: 1) }
end