RubyのObject#then

Object#then (Ruby 3.1 リファレンスマニュアル)

yield_self のエイリアス。selfを引数としてブロックを評価し、ブロックの結果を返す

# thenを使うと
# これを
def user_name(user, hage)
  first_name = user.fetch(:first_name, {})
  first_name.empty? ? first_name : first_name.merge(last_name:)
end

# こう書ける
def user_name(user, hage)
  user.fetch(:first_name, {}).then { |name| name.empty? ? name : name.merge(last_name:) }
  first_name.empty? ? {} : first_name.merge(last_name:)
end

aggregate_failures

https://relishapp.com/rspec/rspec-core/docs/expectation-framework-integration/aggregating-failures

1つのexampleに複数のexpectがあるとき、途中のexpectがfailしてしまうと後続のexpectは実行されない

そうなると後続のexpectは期待結果どおりになったのかがわからなくなってしまう

後続のexpectがエラーになっていたとしたら1個ずつ直していかなければいけない

この問題を解決するのが aggregate_failures

こんなかんじでブロックで囲んだexpectをすべて実行してくれる

it 'レスポンスが正しい値であること' do
  aggregate_failures do
    expect(response).to have_http_status(:success)
    expect(response.body).to include book.title
  end
end

また、文字列を渡すとブロックのラベルとしてテスト結果に出力されるので、まとまりが視認しやすくなる

以下、https://relishapp.com/rspec/rspec-core/docs/expectation-framework-integration/aggregating-failures から引用

# spec/use_block_form_spec.rb
require 'client'

RSpec.describe Client do
  after do
    # this should be appended to failure list
    expect(false).to be(true), "after hook failure"
  end

  around do |ex|
    ex.run
    # this should also be appended to failure list
    expect(false).to be(true), "around hook failure"
  end

  it "returns a successful response" do
    response = Client.make_request

    aggregate_failures "testing response" do
      expect(response.status).to eq(200)
      expect(response.headers).to include("Content-Type" => "application/json")
      expect(response.body).to eq('{"message":"Success"}')
    end
  end
end

これを実行すると以下のように出力される

Failures:

  1) Client returns a successful response
     Got 3 failures:

     1.1) Got 3 failures from failure aggregation block "testing response".
          # ./spec/use_block_form_spec.rb:18
          # ./spec/use_block_form_spec.rb:10

          1.1.1) Failure/Error: expect(response.status).to eq(200)

                   expected: 200
                        got: 404

                   (compared using ==)
                 # ./spec/use_block_form_spec.rb:19

          1.1.2) Failure/Error: expect(response.headers).to include("Content-Type" => "application/json")
                   expected {"Content-Type" => "text/plain"} to include {"Content-Type" => "application/json"}
                   Diff:
                   @@ -1 +1 @@
                   -"Content-Type" => "application/json",
                   +"Content-Type" => "text/plain",
                 # ./spec/use_block_form_spec.rb:20

          1.1.3) Failure/Error: expect(response.body).to eq('{"message":"Success"}')

                   expected: "{\"message\":\"Success\"}"
                        got: "Not Found"

                   (compared using ==)
                 # ./spec/use_block_form_spec.rb:21

     1.2) Failure/Error: expect(false).to be(true), "after hook failure"
            after hook failure
          # ./spec/use_block_form_spec.rb:6
          # ./spec/use_block_form_spec.rb:10

     1.3) Failure/Error: expect(false).to be(true), "around hook failure"
            around hook failure
          # ./spec/use_block_form_spec.rb:12

aggregate_failuresをすべてのテストに適用する

cf. 実用的な新機能が盛りだくさん!RSpec 3.3 完全ガイド - Qiita

spec_helper.rbに以下の設定を追加すると、すべてのテストでaggregate_failuresが適用される

RSpec.configure do |config|
  config.define_derived_metadata do |meta|
    meta[:aggregate_failures] = true unless meta.key?(:aggregate_failures)
  end
end

一部だけ無効にしたい場合は、 aggregate_failures: false を付ける

it "returns a successful response", aggregate_failures: false do
  response = Client.make_request

  expect(response.status).to eq(200)
  expect(response.headers).to include("Content-Type" => "application/json")
  expect(response.body).to eq('{"message":"Success"}')
end