Help us understand the problem. What is going on with this article?

RSpec 3のyield_receiver_to_any_instance_implementation_blocksオプションについて調べてみた

More than 5 years have passed since last update.

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氏のブログに書いてありました。

ブログ原文

Screen Shot 2014-03-22 at 19.49.35.png

日本語訳
Screen Shot 2014-03-22 at 20.17.37.png

が、この説明を読んでも何かピンと来ない・・・。
というわけで、実際にコードを動かしながら確認してみました。

このオプションの目的はブロック引数にインスタンスを渡すかどうかを決めるだけ

実際に動かしてみると、なんてことはありません。
このオプションは、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_blockstrueをセットすると、ブロック引数に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テスト入門

jnchito
SIer、社内SEを経て、ソニックガーデンに合流したプログラマ。 「プロを目指す人のためのRuby入門」の著者。 http://gihyo.jp/book/2017/978-4-7741-9397-7 および「Everyday Rails - RSpecによるRailsテスト入門」の翻訳者。 https://leanpub.com/everydayrailsrspec-jp
https://blog.jnito.com/
sonicgarden
「お客様に無駄遣いをさせない受託開発」と「習慣を変えるソフトウェアのサービス」に取り組んでいるソフトウェア企業
http://www.sonicgarden.jp
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした