Everyday Rails - RSpecによるRailsテスト入門 chapter9

最近、積読状態だった以下を読んでいます。
RSpec学ぶにはなかなか良い本だと思います。
9章の内容が良かったのでご紹介。
leanpub.com

9. 速くテストを書き、速いテストを書く

aggregate_failures

aggregate_failures を使えば、事前処理が同じようなitをまとめて書くことができます。

      it "responds successfully" do
        sign_in @user
        get :index
        aggregate_failures do
          expect(response).to be_success
          expect(response).to have_http_status "200"
        end
      end

aggregate_failures がない状態だと、どこで落ちたかの判別ができないため、従来は it を分けて書くことが推奨されてきました。
しかし、aggregate_failuresを使えば、途中でエラーとなっても、最後までexpectを実行しどのようなエラーがどこで発生したかが分かりやすくなります。

エラー例)

Failures:

  1) ProjectsController#index as an authenticated user responds successfully
     Got 2 failures from failure aggregation block.
     # ./spec/controllers/projects_controller_spec.rb:13:in `block (4 levels) in <top (required)>'

     1.1) Failure/Error: expect(response).to_not be_success
            expected `#<ActionDispatch::TestResponse:0x00007fdf6580fce0 @mon_mutex=#<Thread::Mutex:0x00007fdf6580fc68>, @mo...ders:0x00007fdf65829ca8 @req=#<ActionController::TestRequest:0x00007fdf6580fe98 ...>>, @variant=[]>>.success?` to return false, got true
          # ./spec/controllers/projects_controller_spec.rb:14:in `block (5 levels) in <top (required)>'

     1.2) Failure/Error: expect(response).to have_http_status "201"
            expected the response to have status code 201 but it was 200
          # ./spec/controllers/projects_controller_spec.rb:15:in `block (5 levels) in <top (required)>'

こりゃ使わない手はないでしょう。

doubleとinstance_double

doubleとinstance_doubleの違いについて記載があり、わかりやすい例があったのでご紹介。  
1対多の関係のこう言うモデルがあったとします。 f:id:rochefort:20200531182812p:plain

class User < ApplicationRecord
  has_many :notes

  def name
    [first_name, last_name].join(" ")
  end
end


class Note < ApplicationRecord
  belongs_to :user

  delegate :name, to: :user, prefix: true
end

Noteにuserモデルにdelegateさせるメソッドを生やしていて、これをテストする場合。 通常は、FactoryBotでデータ作成してテストします。

  it "delegates name to the user who created it" do
    user = FactoryBot.create(:user, first_name: "Fake", last_name: "User")
    note = Note.new(user: user)
    expect(note.user_name).to eq "Fake User"
  end

doubleを使うことで、DBへのアクセスを無くしてテストすることができます。

  it "delegates name to the user who created it" do
    user = double("user", name: "Fake User")
    note = Note.new
    allow(note).to receive(:user).and_return(user)
    expect(note.user_name).to eq "Fake User"
  end

一見良さそうに見えますが、例えば、User#name の名称が full_nameなどに変わったとしても テストは成功してしまいます。そこで登場するのが、 instance_double

  it "delegates name to the user who created it" do
    user = instance_double("User", name: "Fake User")
    note = Note.new
    allow(note).to receive(:user).and_return(user)
    expect(note.user_name).to eq "Fake User"
  end

これを使うと、Userモデルに定義されているかどうかまで検証してくれます。(第一引数が大文字になっています。)

See Also

aggregate_failuresやinstance_doubleについても自分のブログで少し言及していたことを忘れていました。。
RSpecのベストプラクティスとRSpec3の新機能 - rochefort's blog http://rochefort.hatenablog.com/entry/2016/02/16/080000