patorashのブログ

方向性はまだない

RSpecでブロック引数をmockで置き換える

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.pryfind_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が呼ばれることを確認することに成功!✌️

参考にしたページ