読者です 読者をやめる 読者になる 読者になる

Tbpgr Blog

Ruby プログラマ tbpgr(てぃーびー) のブログ

rubocop-rspecのMessageExpectation CopとGiven/When/Then

alt

rubocop-rspec には MessageExpectation という Cop(Check項目)があります。
これはデフォルトで allow(foo).to receive(:bar) を推奨し
expect(foo).to receive(:bar) に対して警告を出します。

この警告がでたときに、「ん?」どういうことなんだろう?
と思い、内容を調べることになりました。
(ほとんど隣の席の人が調べてくれたのだけど)

前段

前提として Given / When / Then について説明します。

Given / When / Then は BDDのテストを書く際に利用される記法です。
基本的な考え方として、テストを3つのセクションに分割して記載します。

Syntax Description Example
Given 振る舞いを実行する前の状態を記述します 事前状態の設定やスタブ
When 振る舞いを記述します テスト対象のメソッドの呼び出し
Then 振る舞いの結果を検証します 検証コード

これにより各ブロックごとの役割がひと目で分かり、可読性が高まることがメリットと考えられています。
(このスタイルが読みやすいと思わない人にはメリットになりませんね。
例えば、この記法を強制するとワンライナーのテストを気軽に書きにくい、という点もあります)

テストフレームワークによってはこの記法を強制してくるものもあります。
テストフレームワークがこの記法をサポートしていない場合は、

  • 拡張ライブラリがあれば使う( 例えば rspec-given )
  • 行間を開ける(アナログ対応)
  • コメントをつけておく(アナログ対応)

などで Given / When / Then を明示することになるでしょう。

以下は Given / When / Then と rspec-given について私がまとめた記事です。

qiita.com

MessageExpectationの意図は?

ようやく本題です。
rubocop-rspec のプロジェクトで MessageExpectation を作成したときのプルリクエストは以下です。

Add MessageExpectation cop - Pull Request 169 - backus/rubocop-rspec

動作上は

expect(foo).to receive(:bar)

# exercise system under test

allow(foo).to receive(:bar)

# exercise system under test

expect(foo).to have_received(:bar)

は同じです。

前者はモック時に :bar が呼び出されたかどうか?を記述しています。
これは Given / When / Then でいうなら Given の際に Then の内容を記述していることになります。

それに対して後者は Given と Then が明確にわかれています。
この方が好ましいだろう、というのが MessageExpectation が意図するところです。

サンプル

  # given
  # Open3.capture3 をモックします
  expect(::Open3).to have_received(:capture3).once

  # when
  # some logic

  # then
  # other verification
$ rubocop
Running RuboCop...
Inspecting 51 files
..............................................C....

Offenses:
... RSpec/MessageExpectation: Prefer allow for setting message expectations.

expect ではなく allow を使うように怒られます。

以下のようにすると警告が消えます。

  # given
  # Open3.capture3 をモックします
  allow(::Open3).to receive(:capture3)

  # when
  # some logic

  # then
  expect(::Open3).to have_received(:capture3).once
  # other verification

ここで問題なのは MessageExpectation という Cop 名や
Prefer allow for setting message expectations. という警告メッセージからは
本来の意図を把握しにくく、開発時のプルリクエストを見てようやく内容を把握できる
という点です。

未来

MessageExpectation Cop が誤解をうみやすかったせいか、代替手段として MessageSpies Cop が提供されるようです。
2016/12/20時点の CHANGELOG.md をみると master にマージ済みだが未リリースです。

Add MessageSpies cop for enforcing consistent style of either expect(...).to have_received or expect(...).to >receive, intended as a replacement for the MessageExpectation cop.

MessageSpies Cop - Pull Request 224

関連情報