きり丸の技術日記

技術検証したり、資格等をここに残していきます。

Rubyでモックの呼び出し回数によって返却値を変更する

同一のパラメータでメソッド呼び出しを行っている。しかし、呼び出した回数によって返却値を変更したい。

そのようなユースケースを満たすために素振りしました。

環境

  • Ruby
    • 3.0.2p107
  • Rails
    • 6.0.3.7
  • RSpec

ユースケース

時刻は常に、Time.nowで取得している。時刻を元にして主キーを生成しているが、テスト時に取得する時刻が常に同一だと主キー制約違反が発生してテストができない。

ですので、Time.nowの呼び出し回数によって返却する時刻を変更することで、課題を解決したい。

対応

  1. allowメソッドでクラスをモックにします。
  2. receiveで対象メソッドを指定します
  3. and_returnで複数のパラメータを指定します

and_returnのパラメータが、呼び出す回数とマッピングされます。

第1パラメータが1回目の呼び出しでの返却値、第2パラメータが2回目の呼び出しでの返却値となります。指定したパラメータ数よりも多くの呼び出しをしている場合、最後に指定していたパラメータを常に返却します。

  describe "呼び出す回数ごとに処理する内容を変更する" do
    let(:_20200101) { Time.new(2020, 1, 1, 0, 0, 0) }
    let(:_20210101) { Time.new(2021, 1, 1, 0, 0, 0) }
    before do
      allow(Time).to receive(:now)
        .and_return(_20200101, _20210101)
    end
    it "1回目、2回目以降は引数の違いで異なる値が返却される。3回目と4回目は一致していること" do
      expect(Time.now).not_to eq(Time.now)
      expect(Time.now).to eq(Time.now)
    end
  end

なお、このやり方はリファクタリングをひどく難しくします。リファクタリングのために処理順序を変えるとテストがエラーになってしまう可能性が高いです。テストが壊れやすくなるので、個人的にはオススメしません。Productコード自体は問題ないのも、また厄介です。

  describe "呼び出す回数ごとに処理する内容を変更する" do
    let(:_20200101) { Time.new(2020, 1, 1, 0, 0, 0) }
    let(:_20210101) { Time.new(2021, 1, 1, 0, 0, 0) }
    before do
      allow(Time).to receive(:now)
        .and_return(_20200101, _20210101)
    end
    it "1回目、2回目以降は引数の違いで異なる値が返却される。3回目と4回目は一致していること" do
      # 1回事前にTime.nowを読んでたりすると、エラーになる
      Time.now
      expect(Time.now).not_to eq(Time.now)
      expect(Time.now).to eq(Time.now)
    end
  end

ソースコード

終わりに

正直、困っていたわけではありませんが、本業でよくこのケースでハマったのでRubyでも同様の事象が起こらないか調べました。


この記事がお役に立ちましたら、各種SNSでのシェアや、今後も情報発信しますのでフォローよろしくお願いします。