モックに使えるメソッドのメモ。
この記事ではスタブ、ダブル、モックなどのワードを厳密に区別しません。
ここに書いている内容はほとんど公式ドキュメントからの抜粋です。
https://rspec.info/features/3-12/rspec-mocks/
double#
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_spyやclass_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")
|