esm アジャイル事業部 開発者ブログ

永和システムマネジメント アジャイル事業部の開発者ブログです。

RSpecで引数に特定の値が渡された時だけスタブしたい

はじめに

こんにちは。入社して4年目になりました、wai-doi です。

お仕事でRSpecでテストを書いていて、

「引数に特定の値が渡された時だけスタブしたい」

ということがありました。そのときどのように書けばよいか分からなかったので、今回は調べたこととその方法を書きます。

実行環境

  • Ruby 3.1.2
  • RSpec 3.11.0

サンプルコード

例えば以下の Shopper#buy_fruits メソッドのテストをしたいとします。単純に配列の ['apple', 'banana', 'orange'] を返します。

class Shopper
  def buy_fruits(shop)
    basket = []
    basket << shop.sell('apple')
    basket << shop.sell('banana')
    basket << shop.sell('orange')
    basket
  end
end

class Shop
  def sell(fruit)
    fruit
  end
end

普通に RSpec でテストを書くなら以下のようになります。

describe 'Shopper#buy_fruits' do
  let(:shop) { Shop.new }

  it do
    shopper = Shopper.new
    expect(shopper.buy_fruits(shop)).to eq ['apple', 'banana', 'orange']
  end
end

引数に 'banana' が渡される呼び出しだけをスタブする

ここからが本題です。

このとき、Shop#sell メソッドの引数に 'banana' が渡される呼び出しだけをスタブしたいとします。スタブして 'banana' ではなく nil を返すようにしてみます。

次のようにテストを書くとうまくできます。

describe 'Shopper#buy_fruits' do
  let(:shop) { Shop.new }

  before do
    allow(shop).to receive(:sell).and_call_original
    allow(shop).to receive(:sell).with('banana').and_return(nil)
  end

  it do
    shopper = Shopper.new
    expect(shopper.buy_fruits(shop)).to eq ['apple', nil, 'orange']
  end
end

引数に 'bannana' が渡される呼び出しを特定するために with('banana') を使用しています。

ポイントは、先に and_call_original を書いて、そのあと with('banana').and_return(nil) を書く順番です。逆だとうまくいきません。

今回の方法はこちらの Stack Overflow のページを参考にしました。

stackoverflow.com

上記の方法は柔軟には使えない

ただし、上記の方法が使える場合は限られています。以下の場合には使えません。

同じ引数の呼び出しすべてに影響してしまう

例えば次のような、 Shop#sell の 2 番目と 3 番目の呼び出しが同じ引数の場合です。

class Shopper
  def buy_fruits(shop)
    basket = []
    basket << shop.sell('apple')
    basket << shop.sell('banana')
    basket << shop.sell('banana')
    basket
  end
end

この場合

allow(shop).to receive(:sell).and_call_original
allow(shop).to receive(:sell).with('banana').and_return(nil)

と書いてしまうと、2 番目だけでなく 3 番目の Shop#sell の呼び出しも nil を返してしまいます。 例えば 2 番目の呼び出しだけをスタブしたいといったことはできません。

引数を持たないメソッドには使えない

スタブ対象のメソッド(今回の Shop#sell)が引数をもつ必要があります。引数を持たないメソッドの場合は with でスタブしたい呼び出しを特定できないため、この方法は使えません。 そのため同じメソッド呼び出しが複数ある場合、この行の呼び出しの時だけスタブしたいといったことはできません。

まとめ

RSpecで「引数に特定の値が渡された時だけスタブしたい」場合について書きました。 and_call_originalwith でそのスタブしたい呼び出しを特定することで実現できました。

この記事がどなたかのお役に立てば幸いです。


最後に、株式会社永和システムマネジメントでは、Ruby とアジャイルソフトウェア開発を通じてコミュニティと共生しながら成長したいエンジニアを絶賛募集しています。

agile.esm.co.jp