モックに使えるメソッドのメモ。 この記事ではスタブ、ダブル、モックなどのワードを厳密に区別しません。

ここに書いている内容はほとんど公式ドキュメントからの抜粋です。 https://rspec.info/features/3-12/rspec-mocks/

double

1
dbl = double("Hoge")

doubleメソッドでダブルオブジェクトを作成できます。 これだけではメソッド呼び出しに対応できないので、receiveメソッドで振る舞いを定義します。

1
2
3
dbl = double("Hoge")
allow(dbl).to receive(:foo).and_return("bar")`
dbl.foo # => "bar"

doubleは実オブジェクトに存在しないメソッドも定義できます。 これを避けたい場合は、次に上げるinstance_doubleが使えます。

instance_double

1
2
3
4
5
6
dbl = instance_double("String")
allow(dbl).to receive(:length).and_return(5)
dbl.length # => 5

# Stringオブジェクトに存在しないので、エラーになる
allow(dbl).to receive(:hoge).and_return(5)

実オブジェクトの一部の振る舞いを差し替えたいことが多いと思うので、基本的にはinstance_doubleを使うのが良いでしょう。 クラスメソッドに対しては、class_doubleがあります。

spy

メソッドが呼ばれたことを検証する場合に使います。(いわゆるスパイというやつ)

1
2
3
4
spy = spy("Hoge")
allow(spy).to receive(:foo).and_return("bar")
spy.foo
expect(spy).to have_received(:foo).once

こちらも、instance_spyclass_spyがあります。

receive / receive_message

定義したダブルに応答できるメソッドを定義するにはreceiveメソッドを使います。

1
2
3
4
5
6
allow(dbl).to receive(:foo).and_return("bar")
allow(dbl).to receive(:baz).with(1, 2).and_return("qux")
allow(dbl).to receive_messages(foo: "bar", baz: "qux")

dbl.foo # => "bar"
dbl.baz(1, 2) # => "qux" 

これでメソッドを呼び出し後の応答を固定できます。

as_null_object

ダブルオブジェクトは、デフォルトでは定義されていないメソッドが呼ばれるとエラーになります。 これを避けたい場合は、as_null_objectメソッドを使います。

1
2
dbl = double("Hoge").as_null_object
dbl.unknown_method # エラーにならない

responseの設定

メソッドを呼び出した際の応答を設定する方法はいくつかあります。

and_return

and_returnを定義しない場合、デフォルトではnilが返されます。 nil以外を返したい場合は、and_returnを使います。

1
allow(dbl).to receive(:foo).and_return("bar")

異なる返却値を順番に返したい場合は、次のようにします。

1
2
3
4
5
allow(dbl).to receive(:foo).and_return("first", "second", "third")

dbl.foo # => "first"
dbl.foo # => "second"
dbl.foo # => "third"

and_raise

メソッドが呼び出された際に例外を発生させたい場合は、and_raiseを使います。

1
2
allow(dbl).to receive(:foo).and_raise(StandardError)
allow(dbl).to receive(:foo).and_raise("エラーが発生しました")

and_throw

and_throwを使うと、メソッドが呼び出された際にthrowを発生させることができます。

1
allow(dbl).to receive(:foo).and_throw(:my_symbol)

and_yield

and_yieldを使うと、ブロックを受け取るメソッドが呼び出された際に、指定した値をブロックに渡すことができます。

1
2
3
4
5
allow(dbl).to receive(:foo).and_yield(1).and_yield(2)
dbl.foo { |value| puts value }
# 出力:
# 1
# 2

and_call_original

and_call_originalを使うと、元のメソッドを呼び出すことができます。

1
2
3
4
5
6
7
8
9
class MyClass
  def greet
    "Hello"
  end
end

obj = MyClass.new
allow(obj).to receive(:greet).and_call_original
obj.greet # => "Hello"

特定の引数の場合のみ元のメソッドを呼び出すこともできます。

1
2
3
4
allow(obj).to receive(:greet).with("World").and_call_original
allow(obj).to receive(:greet).with("Everyone").and_return("スタブされた応答が返される")
obj.greet("World") # => "Hello"
obj.greet("Everyone") # => スタブされた応答が返される

allowとxxx_any_instance_of

allow_any_instance_ofを使うと、特定のクラスのすべてのインスタンスに対してメソッドの振る舞いを定義できます。

1
allow_any_instance_of(MyClass).to receive(:foo).and_return("bar")

rspec-mocksは個々のインスタンス向けに設計されているようなので、クラス前提に影響するallow_any_instance_ofはあまり推奨されていません。 代わりに、特定のインスタンスに対してallowを使うことが推奨されています。 また、allow_any_instance_ofは複雑で過去にも多くのバグがあり、使用する際には注意が必要です。

1
2
obj = MyClass.new
allow(obj).to receive(:foo).and_return("bar")