RSpec 3.0から3.3まで一気に上げたので独断と偏見で選んだ注目すべき新機能を紹介する

実際にはほとんど3.3の話ですが、まぁ3.3それだけよさそうなんで、みなさん使ってみてください。

# まずはRSpecのブログにも書いてあったものから。

- [RSpec 3.1 has been released!](http://rspec.info/blog/2014/09/rspec-3-1-has-been-released/)
- [RSpec 3.2 has been released!](http://rspec.info/blog/2015/02/rspec-3-2-has-been-released/)
- [RSpec 3.3 has been released!](http://rspec.info/blog/2015/06/rspec-3-3-has-been-released/)

## (3.2での変更) Core: Performance Improvements

オブジェクトを割り当てる回数を減らして速くしたそうです。RSpecが速すぎて困る人は誰もいません。上げましょう。

## (3.2での変更) Mocks: Mismatched Args Are Now Diffed

`expect(x).to eq(y)` して失敗した時と同じように、`expect(x).to receive(:method_name).with(arg)`と書いて失敗した時も**予測した結果と実際の結果のdiff**が出るようになりました。

## (3.3での変更) Core: New `--only-failures` option

```ruby
RSpec.configure do |c|
  c.example_status_persistence_file_path = "./tmp/rspec-example-status.txt"
end
```

などと`spec_helper.rb`に書いておくと、rspecを実行するたび`./tmp/rspec-example-status.txt`に最後に実行したexampleの状態（ちゃんと中身見てませんが成功したとか失敗したとかの情報でしょう）を記録してくれるそうです。
そして`--only-failures`オプションを使用すると、名前通りこのファイルを見て、「最後に`rspec`を実行した時に**失敗したexampleのみ**実行」してくれるようになりました。素晴らしい！ :smile:

## (3.3での変更) Core: New `--next-failure` option

先ほど紹介した`./tmp/rspec-example-status.txt`に記録した情報を利用して、「最後に`rspec`を実行した時に失敗したexampleのうち、**最初に失敗するexampleまで**実行する」というオプションです。
`--only-failures --fail-fast --order defined`と等価なのだそうです。

## (3.3での変更) Expectations: New `aggregate_failures` API

※この節のサンプルコードは一部 http://rspec.info/blog/2015/06/rspec-3-3-has-been-released/ から引用しています。

例えば :point_down: のように書くと

```ruby
RSpec.describe Client do
  let(:response) { Client.make_request }

  it "returns a successful JSON 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
```

example `it "returns a successful JSON response"` の、各expectationを順番に実行して、1つでもexpectationが失敗した場合、exampleは失敗したとみなされ、**実行を中断**します。

ところが、下記のように`aggregate_failures`とタグを指定すると...。

```ruby
RSpec.describe Client do
  let(:response) { Client.make_request }

  it "returns a successful JSON response", :aggregate_failures 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
```

各expectationを順番に実行してexpectationが失敗した場合でも、**実行を中断しない**ようになります（もちろんexample自体は失敗したことになります）。

これによって、こういうケースで`it`ブロックをちまちま分ける必要がなくなり、結果`before`ブロックを実行する回数が減り、**実行速度を高めたり、より簡潔にexampleを書ける**ようになります。
これは`before`ブロックでDBのレコードをたっぷり追加する、みたいなことをしている場合特に大きなメリットとなるでしょう。

しかし、名前がいかにもRSpecらしくない。言わなくても最初からそうしてほしいなぁ、
... と、思ったところ、下記のように設定すればデフォルトでそうできるんだとか。

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

もしこの設定をした上で「`aggregate_failures`したくない！」と思った時は先ほどのようにtagに`:aggregate_failures`と書く代わりに

```ruby
# ここに書けばこのブロック内すべてのexampleが`aggregate_failures`されます。
RSpec.describe Client, aggregate_failures: false do
  let(:response) { Client.make_request }

  #                                        もちろん個別に指定してもOK!
  it "returns a successful JSON response", aggregate_failures: false 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
```

と書きましょう。

## (3.3での変更) Expectations: Improved Failure Output

色々良くなったようです。特に**`Time`オブジェクトが詳細に表示**されるようになったのが大きいかな？
気づいた時は喜び、そして感謝しましょう。

# ChangeLogも流し読みしてみたよ！

- [rspec-core/Changelog.md at v3.3.1 · rspec/rspec-core](https://github.com/rspec/rspec-core/blob/v3.3.1/Changelog.md)
- [rspec-expectations/Changelog.md at v3.3.0 · rspec/rspec-expectations](https://github.com/rspec/rspec-expectations/blob/v3.3.0/Changelog.md)
- [rspec-mocks/Changelog.md at v3.3.1 · rspec/rspec-mocks](https://github.com/rspec/rspec-mocks/blob/v3.3.1/Changelog.md)
- [rspec-rails/Changelog.md at v3.3.2 · rspec/rspec-rails](https://github.com/rspec/rspec-rails/blob/v3.3.2/Changelog.md)
- [rspec-support/Changelog.md at v3.3.0 · rspec/rspec-support](https://github.com/rspec/rspec-support/blob/v3.3.0/Changelog.md)

で、個人的にめちゃめちゃ大きかったのが以下の2つ。

- Warn when using a bare `raise_error` matcher that you may be subject to false positives. (Jon Rowe, #768)

:point_down: こういう風に、**引数なしで`raise_error`を使用すると、警告**が出るようになりました。

```ruby
expect { subject_method }.to raise_error
```

このように`raise_error`の引数に何も指定しない場合、ブロックの中でどんなエラーが起きようとexampleが成功してしまいます。
例えばブロックの中で実行している`subject_method`の名前が実は間違っていて本当は`subject_function`だったとしましょう。
それでも`NoMethodError`が発生するので、このexampleは成功してしまうのです。
どんなエラーでも成功する、ということにすると、意図通り実装ができていないのに成功してしまうexampleを書いてしまう危険性が非常に高いのです。
こうした弊害を防ぐため、警告を出すようになりました。

これ、[関係するIssue](https://github.com/rspec/rspec-expectations/issues/655)を個人的にwatchしていたほど気になっていた問題であったため、修正されたのは非常にありがたいです。
ただ、個人的な事情を申しますと、CircleCIにしてから警告をあまり見なくなってしまったため、その習慣を改めないといけませんね...。

- Ensure expectations that raise eagerly also raise during RSpec verification. This means that if exceptions are caught inside test execution the test will still fail. (Sam Phippen, #884)

:point_down: 例えばこういう実装で、

```ruby
def just_hello
  puts "hello"
  frined.accept_hello_of(self)
rescue Exception
  warn "I JUST WANT TO SAY HELLO!! DON'T DISTURB!!"
end
```

:point_down: こういうexampleだった場合、

```ruby
it 'says hello to no other person' do
  expect(friend).not_to receive(:accept_hello_of)
  x.just_hello
end
```

RSpec 3.3より前までは、expectationに反して`friend`が`accept_hello_of`しているにも関わらず、テストが通ってしまっていました。
なぜでしょう :question:

`just_hello`の実装の`friend.accept_hello_of(self)`が呼ばれた時点でrspec-mocksの例外が発生するも、`just_hello`の中で`rescue Exception`しているため、その例外まで`rescue`され、無視されてしまうからです。

RSpec 3.3からは、こうしたケースに対応するため、rspec-mocksが例外を発生させたとしても、exampleの実行終了後、mockのverifyをしたタイミングで必ず失敗するよう修正されました。

これによって、嬉しい弊害が発生しました。
どういうわけか我々のコードではこの`rescue Exception`が多用されており、お陰でRSpec 3.3に上げた途端**落ちるようになったexampleが17件も**あったのです。
みなさんも移行の際はその辺を念頭に置きつつ、`rescue Exception`にはくれぐれも気をつけましょう。
安易に使ってはいけません。 :warning: