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) }
とかするのかな...
奥が深い。