RSpec の partial double とはなにか
RSpec には #and_call_original
というメソッドがあり、これを使いたい場面があったのですが、使い方を間違えて次のようなエラーが発生しました。
xxx is a pure test double. `and_call_original` is only available on a partial double.
このエラーを見て、pure test double
と partial double
の違いがあまりわかっていないと感じ、partial double について色々調べてみました。
Pure と Partial
Partial double を理解するには、対照的な概念である Pure test double と合わせて理解するのが良さそうです。
Pure test double
まず Pure test double ですが、これは double
や instance_double
などのメソッドを用いて作られたテストダブルを指します。
# 以下はどちらも pure test double を作る
double(Foo)
instance_double(Foo)
その他にも spy
や instance_spy
、class_double
、 object_double
なども同じくです。RSpec でテストを書いていれば日常的に使っているメソッドではないでしょうか。
Partial double
一方の Partial double ですが、こちらはまず実際のオブジェクトがあり、それに対してテストダブルのような振る舞いができるように拡張したものです。 テストダブルのような振る舞い、というのは例えばメソッドの返り値をモックするようなことです。
例として Rails アプリケーション内に User モデルあり、これの find
メソッドをモックしたいようなケースがある場合、次のように書けます。
dummy_user = double('dummy user')
allow(User).to receive(:find).and_return(dummy_user)
そしてこのコードにおいて、User
は Partial double です。
Partial double は名前の通り "部分的な" テストダブルであるため、明示的に allow
でモックした find
以外は元の実装のままになります。
class HelloWorld
def self.hello
'Hello'
end
def self.world
'World'
end
end
RSpec.describe HelloWorld do
it do
allow(HelloWorld).to receive(:hello).and_return('GoodBye')
expect(HelloWorld.hello).to eq 'GoodBye' # 'GoodBye' を返すようにモックされている
expect(HelloWorld.world).to eq 'World' # world メソッドはそのまま
end
end
上の例はクラスメソッドですが、インスタンスメソッドでも同様です。以下は、HelloWorldクラスのメソッドをすべてインスタンスメソッドに置き換えた例です。
class HelloWorld
def hello
'Hello'
end
def world
'World'
end
end
RSpec.describe HelloWorld do
it do
hello_world = HelloWorld.new
allow(hello_world).to receive(:hello).and_return('GoodBye')
expect(hello_world.hello).to eq 'GoodBye' # 'GoodBye' を返すようにモックされている
expect(hello_world.world).to eq 'World' # world メソッドはそのまま
end
end
まとめ
まとめると、double
や instance_double
などのメソッドを使って明示的にテストダブルを作っている場合は Pure で、そうではない場合は Partial と判断できそうです。少し調べたのですが、Pure か Partial かを正確に判断できるメソッドなどは存在しないようでした。
and_call_original
や and_wrap_original
といったメソッドは、Partial test double の方しか使えません。メソッドの役割とテストダブルの違いを理解しておくと自然とそうなることがイメージしやすくなると思います。