2024-02-12

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 doublepartial double の違いがあまりわかっていないと感じ、partial double について色々調べてみました。

Pure と Partial

Partial double を理解するには、対照的な概念である Pure test double と合わせて理解するのが良さそうです。

Pure test double

まず Pure test double ですが、これは doubleinstance_double などのメソッドを用いて作られたテストダブルを指します。

# 以下はどちらも pure test double を作る
double(Foo)
instance_double(Foo)

その他にも spyinstance_spyclass_doubleobject_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

まとめ

まとめると、doubleinstance_double などのメソッドを使って明示的にテストダブルを作っている場合は Pure で、そうではない場合は Partial と判断できそうです。少し調べたのですが、Pure か Partial かを正確に判断できるメソッドなどは存在しないようでした。

and_call_originaland_wrap_original といったメソッドは、Partial test double の方しか使えません。メソッドの役割とテストダブルの違いを理解しておくと自然とそうなることがイメージしやすくなると思います。

参考URL