セキュリティ系の勉強、その他開発メモとか雑談. GithubはUnity触っていた頃ものがメイン Twitterフォローもよろしくです

【RSpec】before句がdescribeスコープ外にも影響する

環境

RSpec 3.8
ruby 2.5.3p105 (2018-10-18 revision 65156) [x86_64-darwin18]
Rails 5.2.2

問題

ひとまずテストする内容だけ列挙して後から中身を実装していこうなんて考えることがあります。そして以下の状況になった時にひとまず走らせてみるかと思うこともあります。

describe HogeController,type: :request do
  let(:user){create(:user)}                  # factory_botでユーザ生成

  describe "GET #get" do
    let(:get_page){get hoge_path}
    before{  login_as(user) }                # ログインするヘルパー
  
    context "ログインしてアクセス" do
      it "has status 200" do                   # レスポンスが帰ってくる
        get_page
        expect(response).to eq have_http_status(:ok)
      end
      it "別ページへリダイレクトしない" do  # ここはまだ実装してない
      end
    end
  end  
  
  describe "GET #create" do
    let(:post_create){post hoges_path, params:{~~}}
    
    context "ログインしていなかったら" do
      it "ログインページへリダイレクト" do
        post_create
        expect(response).to redirect_to(login_path)    # ここでエラー。ログイン状態での処理が成功していることになっている。以前は成功しているテスト。
      end
    end
  end
end

このコントローラでは、アクションの前にログインしていなかったらログインページへリダイレクトする様にbefore_actionにて定義されています。しかし、上のテストを走らせるとcreateのテストでコケます。getテストのスコープ内ではbefore句でログイン処理を定義していますが、これはcreateのスコープ外なので影響されないはずです。

結論

getテストのスコープの最後の未実装のテストが悪さをしている様です。

検証

describe HogeController,type: :request do
  let(:user){create(:user)}                  # factory_botでユーザ生成

  describe "GET #get" do
    let(:get_page){get hoge_path}
    before{  login_as(user) }                
  
    context "ログインしてアクセス" do
      it "has status 200" do                   
        get_page
        expect(response).to eq have_http_status(:ok)
      end
            # 未実装の処理を削除
    end 
  end  
  
  describe "GET #create" do
    let(:post_create){post hoges_path, params:{~~}}
    
    context "ログインしていなかったら" do
      it "ログインページへリダイレクト" do
        post_create
        expect(response).to redirect_to(login_path)   
      end
    end
  end
end

結果:テスト成功

describe HogeController,type: :request do
  let(:user){create(:user)}                  # factory_botでユーザ生成

  describe "GET #get" do
    let(:get_page){get hoge_path}
    before{  login_as(user) }                
  
    context "ログインしてアクセス" do
      it "has status 200" do                   
        get_page
        expect(response).to eq have_http_status(:ok)
      end
      it "別ページへリダイレクトしない" do
        get_page   # とりあえず何か書いてみる
      end
    end
  end  
  
  describe "GET #create" do
    let(:post_create){post hoges_path, params:{~~}}
    
    context "ログインしていなかったら" do
      it "ログインページへリダイレクト" do
        post_create
        expect(response).to redirect_to(login_path)   
      end
    end
  end
end

結果:テスト成功

以上より、中身が未実装のものがあると、before句の処理内容がロールバックされずに残っていると考えられるかなと。

検証2

describe HogeController,type: :request do
  let(:user){create(:user)}                  # factory_botでユーザ生成

  describe "GET #get" do
    let(:get_page){get hoge_path}
    before{  login_as(user) }                
  
    context "ログインしてアクセス" do
      it "has status 200" do                   
        get_page
        expect(response).to eq have_http_status(:ok)
      end
      it "別ページへリダイレクトしない" do
        get_page   # とりあえず何か書いてみる
      end
      it "未実装のテストがあるよ" do # ここのテストはそのまま
      end
    end
  end  
  
  describe "GET #create" do
    let(:post_create){post hoges_path, params:{~~}}
    
    it "ここに未実装のテスト" do  # ここに未実装のテストをさらに書きます。
    end
    context "ログインしていなかったら" do
      it "ログインページへリダイレクト" do
        post_create
        expect(response).to redirect_to(login_path)   
      end
    end
  end
end

結果:テスト失敗

describe HogeController,type: :request do
  let(:user){create(:user)}                  # factory_botでユーザ生成

  describe "GET #get" do
    let(:get_page){get hoge_path}
    before{  login_as(user) }                
  
    context "ログインしてアクセス" do
      it "has status 200" do                   
        get_page
        expect(response).to eq have_http_status(:ok)
      end
      it "別ページへリダイレクトしない" do
        get_page   # とりあえず何か書いてみる
      end
      it "未実装のテストがあるよ" do # ここのテストはそのまま
      end
    end
  end  
  
  describe "GET #create" do
    let(:post_create){post hoges_path, params:{~~}}
    
    it "ここに未実装のテスト" do 
      post_create                              # とりあえず何か実装してみる 
    end
    context "ログインしていなかったら" do
      it "ログインページへリダイレクト" do
        post_create
        expect(response).to redirect_to(login_path)   
      end
    end
  end
end

結果:成功
context"ログインしていなかったら"句の該当テスト前に、上の二つと同じ様にテストを挟んでみても、同じ結果になりました。it前で実行されたbefore句の内容は何か処理がなければ、ロールバックされない様ですね。詳しいことを知っている方がいれば、教えていただけると嬉しいです。