RSpec 3で追加された不思議なオプション
こちらのQiita記事を参考にしながら、「Everyday Rails - RSpecによるRailsテスト入門」サンプルアプリケーションをRSpec 3にアップグレードしていました。
@yujinakayamaさんが作成されたTranspecという便利なgemを使うと、あら不思議、既存のテストコードがRSpec 3対応のものに書き換えられてしまいます。
ただ、diffを確認していたところ、以下のrspec_helper.rb
の変更が気になりました。
# spec_helper.rb
config.mock_with :rspec do |c|
c.yield_receiver_to_any_instance_implementation_blocks = true
end
このオプションは何を意味しているのでしょうか?
開発者ブログになんか書いてある
このオプションに関する説明はRSpec開発者のmyronmarston氏のブログに書いてありました。
が、この説明を読んでも何かピンと来ない・・・。
というわけで、実際にコードを動かしながら確認してみました。
このオプションの目的はブロック引数にインスタンスを渡すかどうかを決めるだけ
実際に動かしてみると、なんてことはありません。
このオプションは、any_instance
を使っているメソッドでブロックを使ったときに、そのインスタンスをブロック引数として渡すかどうかの違いを決めるためのオプションでした。
上記のブログでも出ている以下のようなコードを例に挙げます。
allow_any_instance_of(User).to receive(:age) do |user|
((Date.today - user.birthdate) / 365).floor
end
yield_receiver_to_any_instance_implementation_blocks
にtrue
をセットすると、ブロック引数にuser
が渡されます。
false
をセットすると、ブロック引数に何も渡されません。(なので、user.birthdate
がエラーで落ちます)
RSpec 2.14ではブロック引数に何も渡さないので、RSpec 3で挙動を合わせる必要があればfalse
を設定します。
また、RSpec 3ではこのオプションのデフォルト値はtrue
になっているので、何も設定しなければuser
が渡されるようになっています。
おまけ: このオプションをfalseにする必要がある場合
2014.03.23追記
@yujinakayamaさんが「どっちでもいいわけではない」理由をこの投稿のコメントに書いてくれました!
なので、以下の僕の書いた内容は無視して、@yujinakayamaさんのコメントを参考にしてくださいm(_ _)m
========
個人的な感想を言えば、「別にどっちでもえーやん。ブロック引数を渡したって既存のspecが壊れることは滅多にないでしょうに」と思いました。
なので、このオプションは「常にtrue
」でも実害がないと思うのですが、false
にしなければならないケースをムリヤリ考えてみました。
# user.saveは常にtrueを返すようにする
f = ->{ true }
allow_any_instance_of(User).to receive(:save, &f)
false
にしなければ落ちてしまうケースは、上のようなテストコードを書いていた場合です。
ブロックにラムダを渡している場合は引数の数が厳密にチェックされるので、ブロック引数が渡されると引数の数が合わないと怒られてエラーになります。
が、おそらくレアケースだと思いますし、最悪エラーが出たらスペックを書き直せばいいと思うのですが、どうなんでしょう??
まとめ
ブロック引数が渡されるようになった背景は、クラスのインスタンスがないと凝った値を返したりするときに不便、ということみたいです。
確かに、その必要性については僕も十分理解できます。ないより、あった方が絶対便利ですもんね。
というか、any_instance
の存在は知っていましたが、ブロックを使えることは知らなかったので、今回の件で「こんなことができるんだ」と勉強になりました。(^^;)
お知らせ
- Everyday Rails - RSpecによるRailsテスト入門はRSpec 3がリリースされ次第、RSpec 3対応版に無料アップデートされる予定ですので、RSpecの勉強をしたい方は参考にしてみてください。