RSpec 3.6 がリリースされました!

Sam Phippen, Myron Marston, Jon Rowe, Yuji Nakayama

May 4, 2017

RSpec 3.6 がリリースされました! 私たちは semantic versioning への準拠を約束しているので、 既に RSpec 3 をお使いの場合、このバージョンへのアップグレードは簡単なはずです。 しかし、もしバグを見つけた場合は教えてください。 できるだけ早く修正をし、パッチ版をリリースします。

RSpec は世界中のコントリビュータと共に、コミュニティ主導のプロジェクトであり続けます。 今回のリリースには、 50 人以上のコントリビュータによる 450 以上のコミットと、 120 以上の pull request が含まれています!

このリリースに向けて力になってくれたみなさん、ありがとう!

主な変更

Core: Example の外で発生したエラーが rescue され、きれいに表示されるように

これまでのバージョンの RSpec では、spec ファイルの読み込み中や :suite フックの実行中にエラーが発生した場合、 Ruby インタプリタはクラッシュし、生のバックトレースすべてが表示されていました。 RSpec 3.6 では、example の外で発生したエラーは rescue され、 フィルタされたバックトレースとエラー元のコードがきれいに表示されるようになります。 例えば before(:suite) フックで発生したエラーは以下のように表示されます。

Errors outside example execution

この実装を手伝ってくれた Jon Rowe、ありがとう。

Core: 出力先が TTY の場合、自動でカラー出力が有効に

これまでの RSpec では、カラー出力をしたい場合、 出力先がターミナルであろうが、ファイルであろうが、CI 環境であろうが、 常に --color オプションを指定する必要がありました。 RSpec 3.6 では、出力先が TTY の場合はカラー出力が自動的に有効化されるようになりました。 これまで通り、出力先に関わらず常にカラーを有効にしたい場合は --color オプションを使えますし、 --no-color オプションを使えば TTY でカラー出力を明示的に無効化することもできます。

この機能を追加してくれた Josh Justice に感謝します。

Core: config.fail_if_no_examples

通常、example が一つも定義されていない場合、RSpec は成功を意味する exit code 0 と共に終了しますが、 今回追加された config.fail_if_no_examples を有効にすると、 そういった場合に失敗を意味する exit code 1 で終了するようになります。 これは CI 環境において有用で、 例えば RSpec の実行対象とする spec ファイルのパスのパターンを間違って設定してしまったような場合に、 それを検出することができます。

RSpec.configure do |config|
  config.fail_if_no_examples = true
end

これを実装してくれた Ewa Czechowska に心から感謝します。

Expectations: changesatisfy マッチャの失敗時メッセージの改善

change マッチャと satisfy マッチャはブロックを受け取ることができます。 change マッチャの場合は「何が」変化するのを期待するか、 satisfy マッチャの場合はテスト対象が「どうあるべきか」をブロックで表現します。 これまで、これらのマッチャの失敗時のメッセージは非常に抽象的でした。 例えば以下のような spec で、

RSpec.describe "`change` and `satisfy` matchers" do
  example "`change` matcher" do
    a = b = 1

    expect {
      a += 1
      b += 2
    }.to change { a }.by(1)
    .and change { b }.by(1)
  end

  example "`satisfy` matcher" do
    expect(2).to satisfy { |x| x.odd? }
            .and satisfy { |x| x.positive? }
  end
end

これまでの失敗時のメッセージは “expected result to have changed by 1, but was changed by 2” や、 “expected 2 to satisfy block” というような形でした。 これらのメッセージの内容は間違っていませんが、 二つのマッチャのどちらが失敗したのかを区別するためのヒントがありませんでした。

RSpec 3.6 では、失敗時の出力は以下のようになります。

Failures:

  1) `change` and `satisfy` matchers `change` matcher
     Failure/Error:
       expect {
         a += 1
         b += 2
       }.to change { a }.by(1)
       .and change { b }.by(1)

       expected `b` to have changed by 1, but was changed by 2
     # ./spec/example_spec.rb:5:in `block (2 levels) in <top (required)>'

  2) `change` and `satisfy` matchers `satisfy` matcher
     Failure/Error:
       expect(2).to satisfy { |x| x.odd? }
               .and satisfy { |x| x.positive? }

       expected 2 to satisfy expression `x.odd?`
     # ./spec/example_spec.rb:13:in `block (2 levels) in <top (required)>'

Yuji Nakayama による素晴らしい取り組みのおかげで、 RSpec は Ripper を使って ブロックのスニペットを抽出し、失敗時のメッセージに含めるようになりました。 もしブロックのスニペットが1行に収まるシンプルなものでない場合は、従来通りの抽象的なメッセージを出力します。

Expectations: マッチャの別名や否定マッチャの定義が example group ごとにできるように

RSpec 3 では alias_matcher が追加され、 マッチャの別名 (matcher aliases) を定義して可読性を高めることができるようになりました。 また、3.1 では 否定マッチャ (negated matchers) を定義するための define_negated_matcher が追加されました.

これまでのバージョンでは、これらのメソッドを使って新しく定義されたマッチャは、 グローバルスコープに定義されていました。 RSpec 3.6 からは、alias_matcherdefine_negated_matcher を example group(describecontext など)のスコープで呼び出せるようになり、 それによって定義されたマッチャは、その example group 自身とその内側の example group のみで有効になります。

RSpec.describe 'scoped matcher aliases' do
  describe 'example group with a matcher alias' do
    alias_matcher :be_a_string_starting_with, :start_with

    it 'can use the matcher alias' do
      expect('a').to be_a_string_starting_with('a')
    end
  end

  describe 'example group without the matcher alias' do
    it 'cannot use the matcher alias' do
      # 上で定義されたマッチャの別名はここでは利用できず、テストは失敗する
      expect('a').to be_a_string_starting_with('a')
    end
  end
end

この機能に貢献してくれた Markus Reiter、ありがとう。

Mocks: without_partial_double_verification

RSpec 3.0 では インターフェイス検証付きダブル (verifying doubles) が追加されました。 インターフェイス検証付きダブルを使うと、あなたが作ったスタブやモックが、 その対象オブジェクトのインターフェイスを正確に模倣できているかを検証することができます。 今回追加された without_partial_double_verification を使うと、 そのブロックの内側でこの挙動を無効化することができます。 例えば、

class Widget
  def call(takes_an_arg)
  end
end

RSpec.describe Widget do
  it 'can be stub with a mismatched arg count' do
    without_partial_double_verification do
      w = Widget.new
      allow(w).to receive(:call).with(1, 2).and_return(3)
      w.call(1, 2)
    end
  end
end

もしこのテストで without_partial_double_verification を使わなかった場合、 Widget クラスの call メソッドを本来の実装とは異なる数の引数でスタブしようとしているため、 テストは失敗します。 この機能は、ビュー内でローカル変数をスタブしようとした時に発生する、 部分的ダブル (partial double) の問題に対処するために追加されました。 詳細については この issue と、 そこからリンクされている rspec-rails の issue を参照してください。

この機能を追加してくれた Jon Rowe に心から感謝します。

Rails: Rails 5.1 のサポート

RSpec 3.6.0 では Rails 5.1 がサポートされました。 Rails 5.1 の API には大きな変更はなかったため、今回のアップグレードはスムーズにできるはずです。 あなたの Rails アプリケーションの RSpec をただ最新バージョンにアップグレードするだけで、 他に作業は必要ありません。

Rails の system tests はまだサポートされていませんが、近い将来サポートする予定です。

Stats:

Combined:

rspec-core:

rspec-expectations:

rspec-mocks:

rspec-rails:

rspec-support:

Docs

API Docs

Cucumber Features

Release Notes

RSpec Core (combining all betas of RSpec 3.6.0)

3.6.0 / 2017-05-04

Full Changelog

Enhancements:

Bug Fixes:

3.6.0.beta2 / 2016-12-12

Full Changelog

Enhancements:

3.6.0.beta1 / 2016-10-09

Full Changelog

Enhancements:

RSpec Expectations (combining all betas of RSpec 3.6.0)

3.6.0 / 2017-05-04

Full Changelog

Enhancements:

Bug Fixes:

3.6.0.beta2 / 2016-12-12

Full Changelog

Bug Fixes:

3.6.0.beta1 / 2016-10-09

Full Changelog

Bug Fixes:

RSpec Mocks (combining all betas of RSpec 3.6.0)

3.6.0 / 2017-05-04

Full Changelog

Bug Fixes:

3.6.0.beta2 / 2016-12-12

Full Changelog

Enhancements:

3.6.0.beta1 / 2016-10-09

Full Changelog

Bug Fixes:

RSpec Rails (combining all betas of RSpec 3.6.0)

3.6.0 / 2017-05-04

Full Changelog

Enhancements:

Bug Fixes:

3.6.0.beta2 / 2016-12-12

Full Changelog

Enhancements:

3.6.0.beta1 / 2016-10-09

Full Changelog

Enhancements:

RSpec Support (combining all betas of RSpec 3.6.0)

3.6.0 / 2017-05-04

Full Changelog

Enhancements:

3.6.0.beta2 / 2016-12-12

Full Changelog

No user-facing changes.

3.6.0.beta1 / 2016-10-09

Full Changelog

Bug Fixes: