質問: よろしくお願いしますm(__)m Rspecでテストケースを書いていて疑問に思った点がありました。 自分なりに調べて理解したつもりなのですが、 私の解釈があっているかどうかチェックしてただけないでしょうか。 間違っている箇所がありましたら ご指摘いただけると幸いです。 どうか宜しくお願い致します!!!! ///////////////////////////////ここから テストケースでモックやスタブを作るということは テストを走らせた時のアプリケーションの挙動を変更するということである。 ``` class Hoge def hoge "hogehoge" end end ``` というメソッドがあって ``` let(:class) { described_class.new } it do allow(class).to receive(:hoge).and_return('piyopiyo') end ``` というスタブを作ったら、 テストを実行し、hogeメソッドが実行された時に"piyopiyo"が返り値となるようになる。 binding.pryで止めてhogeを実行すると以下のようになる。 > > [1] pry(#)> hoge > > "piyopiyo" > > > ちなみに ``` allow(class).to receive(:hoge) ``` と`and_return('piyopiyo')`を指定しないと、 > > [1] pry(#)> hoge > > nil > > > となる 疑問点 ---- 今回疑問に思った箇所は ``` # class class Hoge NotificationMailer.delay.auto_contract_renewal(plan) end # rspec RSpec.describe Hoge do let(:delay) { double('delay') } it do allow(NotificationMailer).to receive(:delay).and_return(delay) expect(delay).to receive(:auto_contract_renewal).exactly(10).times end end ``` ここの`expect(delay)`の箇所でdelayが`:auto_contract_renewal`メソッドを持ってる体でテストを行えるのか? が疑問だった。 だってdelayってただの空のダブルオブジェクトでしょ? 検証 --- 本来は`NotificationMailer.delay`を実行すれば、delayメソッドがオブジェクト(以下オブジェクト1とする)を返す。 そしてそのオブジェクト1が`auto_contract_renewalメソッド`を持っている。 スタブを作成してテストを実行すると 本来`NotificationMailer.delay`が返すオブジェクトがダブルオブジェクトになる。 ダブルとは『代役』とか『影武者』という意味である。 ダブルオブジェクトとは1の代役である なのでオブジェクト1が持っているメソッドを使えるように振る舞うことが出来る。 ここでもbinding.pryで止めて検証 ``` [16] pry(#)> NotificationMailer.delay # [18] pry(#)> NotificationMailer.delay.class RSpec::Mocks::Double < Object [19] pry(#)> NotificationMailer.delay.methods.grep(/auto/) [ [0] auto_contract_renewal(*args, &block) # [1] autoload_all(*paths) RSpec::Mocks::Double (RequireAll) [2] autoload_rel(*paths) RSpec::Mocks::Double (RequireAll) ] ``` となる。 ダブルオブジェクトが`auto_contract_renewal`と持っている。 これが > > ここの`expect(delay)`の箇所でdelayが`:auto_contract_renewal`メソッドを持ってる体でテストを行えるのか? > > が疑問だった。 > > > の答えになった。 ///////////////////////////////ここまで
回答: 質問の意図からずれてたら済みませんが、以下見解です。 ``` expect(delay).to receive(:auto_contract_renewal).exactly(10).times ``` このコード記述の時点ではdelay.auto_contract_renewalが呼ばれるというわけでは無く、「delayオブジェクトは10回auto_contract_renewalメソッドが呼ばれる」というモック(テスト仕様)を定義したことになるので、RSpecはこのテスト仕様の通りauto_contract_renewalが呼ばれるかをチェックするためにメソッドを用意してメソッドの呼び出される回数のカウントを開始しているのだろうと思います。 疑問点にかかれたHogeクラスとそのテストケースだとdelayメソッドが呼ばれるのはクラスロード時の1回のみですので、it内で丁度10回auto_contract_renewalメソッドが呼ばれるトリガーとなる呼び出しが存在しないため必ずテストは失敗するかと思います。 テストとして成り立たせるためにはモックを記述した後にそれが指定したとおりに呼ばれるためのトリガとなるコードを記述させなくてはいけません。クラスロードだと呼び出しにくいのですが、、、例えばauto_contract_renewalが2回必ず呼ばれるメソッドがあったとしたら、それを5回呼び出してみる等になります。