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

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

controller specその2

問題
つぎのうち意図しない動きをするspecはどれでしょう?

context 'question1' do
  subject { get :new }
  it { expect(assigns(:hoge)).to be_new_record } #A
  it { should render_template('new') }   #B
end

context 'question2' do
  subject { response }
  before { get :new }
  it { expect(assigns(:hoge)).to be_new_record } #C
  it { subject.should render_template('new') }  #D
end

答えはAです。
この原理は言われてみればそうなんですが、実際ぷちハマリするまで気づかなかった。subject {} ってあくまで遅延評価なんですね。要するに暗黙の主語を使わない限り評価されません。だからit { expect(assigns:(hoge)).to ... }とかやってもit {}に入ったからsubjectが実行されるなんてのはないわけで。

その点beforeなら必ずit {}に入ったら評価してくれます。

しかしながらsubjectは遅延評価されてbeforeよりもあとに来るという点で都合がいいことがあります。

それは一番先頭にsubject { get :new }を持ってきて、内側のcontextでbefore {}を書くことで状態の条件分岐が非常に楽になる点。

subject { get :index }
context 'user has some blogs' do
  before { create(:blog, user: user) }
  it { should render_template('index') }
end

context 'user has no blog' do
  it { should redirect_to new_blog_path }
end

beforeブロックは外側から評価されるので仮にsubject { get :index }をsubject { resonse }; before { get :index}に書き換えると意図したテストでなくなります。

外側にbeforeを残したまま内側で条件を使いたいなら外側を

before do
  create_blog
  get :index
end
とかして内側で
let(:create_blog) { nil }
let(:create_blog) { create(:blog, user: user) }

とかするのかな...

奥が深い。