RSpecで、ブロック引数に渡されたオブジェクトが、とあるメソッドを実装しているかどうかによって処理を変える件のテストをしたかったのだけれど、どうやったらいいかわからなかったので調べました。
テストしたい処理
class Hoge < ApplicationRecord def foo # 略 end end class Fuga # @param [Class] model_class ApplicationRecordを継承したクラス def initialize(model_class) @model_class = model_class end def execute! @model_class.find_each do |record| # fooメソッドが実装されていたらfooを実行したい value = if record.respond_to?(:foo) record.foo else record.bar end # 略 end end end
これで、model_class#foo
があれば、それが実行されていることを確認したかった。
やったこと
【NG】stubでnewの戻り値をmockに置き換える
当初はHoge#new
メソッドを置き換えておいたらええんちゃうか?と思ったけれど、ダメでした。binding.pry
をfind_each
内のループに仕込んでも到達しなかったので、やめました。
RSpec.describe Fuga do context 'fooメソッドがある場合' do let(:record) { instance_double(Hoge) } before do allow(Hoge).to receive(:new).and_return(record) # NG # record.respond_to?(:foo) の戻り値をtrueにstubしておく allow(record).to receive(:respond_to).with(:foo).and_return(true) end it 'Hoge#fooが呼ばれること' do expect(record).to receive(:foo) described_class.new(Hoge).execute! end end end
【OK】stubでfind_eachのブロック引数をmockに置き換える
and_yield
を使えば、ブロック引数を置き換えることができることを知りました。
RSpec.describe Fuga do context 'ApplicationRecord#fooメソッドがある場合' do let(:record) { instance_double(Hoge) } before do allow(Hoge).to receive(:find_each).and_yield(record) # OK # record.respond_to?(:foo) の戻り値をtrueにstubしておく allow(record).to receive(:respond_to).with(:foo).and_return(true) end it 'fooメソッドが呼ばれること' do expect(record).to receive(:foo) described_class.new(Hoge).execute! end end end
これで無事にHoge#foo
が呼ばれることを確認することに成功!✌️