RSpecモックメモ

double

完全に偽物のオブジェクトを作る。allowなどで定義されていないメソッドを呼び出すとテストが失敗する。

d = double('object')
d.hoge #=> 失敗

d = double('object', hoge: 1, piyo: 2)
expect(d.hoge).to eq 1 #=> 成功

d = double('object')
allow(d).to receive(:hoge)
expect(d.hoge).to eq nil

d = double('object')
allow(d).to receive_messages(hoge: 1)
expect(d.hoge).to eq 1

spy

ほとんどdoubleと同様だが、allowなどで定義していなくても任意のメソッドを呼び出せる。コードは確認していないが、double(...).as_null_objectのショートカットっぽい。

instance_double

ほとんどdoubleと同様だが、allowなどで定義するメソッドが引数で指定したクラスのインスタンスメソッドとして存在していないとテストが失敗する。

d = instance_double('Foo')
# Foo#hoge(a, b)というインスタンスメソッドが存在しないとテストが失敗する
allow(d).to receive(:hoge).with('1', '2').and_return(1)

class_double

ほとんどinstance_doubleと同様だが、こちらはクラスメソッドに対して適用される。

class Foo
  HOGE = 1
end

it do
  d = class_double('Foo')
  expect(d::HOGE).to eq 1 # エラーになる

  d = class_double('Foo').as_stubbed_const(transfer_nested_constants: true)
  expect(d::HOGE).to eq 1 # エラーにならない
end

allow

オブジェクトのメソッドを差し替える。以下の例はdoubleに対してallowを呼び出しているが、通常のオブジェクトに対しても呼び出すことができる。

d = double('object')
allow(d).to receive(:hoge)
expect(d.hoge).to eq nil

d = double('object')
allow(d).to receive_messages(hoge: 1)
# allow(d).to receive(:hoge).and_return(1)とも書ける
expect(d.hoge).to eq 1

and_return

allowで差し替えたメソッドの戻り値を指定する。

o = Foo.new
allow(o).to receive(:hoge).and_return(1)
o.hoge #=> 1が返る
# 複数の引数を指定した場合はn番目の引数がn回目の呼び出しの戻り値になる
allow(o).to receive(:piyo).and_return(1, 2, 3)
o.piyo #=> 1
o.piyo #=> 2
o.piyo #=> 3
o.piyo #=> 3 指定した引数個数以上の呼び出し回数になった場合は最後の引数が返る

and_raise

allowで差し替えたメソッドを呼び出した時に例外を発生させる。

o = Foo.new
allow(o).to receive(:hoge).and_raise('例外メッセージ')
expect { o.hoge }.to raise_error '例外メッセージ'

and_throw

Kernel.#throwの機能。必要ならThrowingを参照。

and_yield

allowで差し替えたメソッドのブロック引数を指定する。

o = Foo.new
allow(o).to receive(:hoge).and_yield(1, 2)
o.hoge { |a, b|
  expect(a).to eq 1
  expect(b).to eq 2
}

# and_yieldをメソッドチェーンすると複数回ブロックが呼ばれる
allow(o).to receive(:hoge).and_yield(1, 2).and_yield(:x, :y)
list = []
o.hoge { |a, b|
  list << a
  list << b
}
expect(list).to eq [1, 2, :x, :y]

and_call_original

allowで差し替えたメソッドのうち特定の引数の場合だけ差し替えたい、あるいは特定の引数の場合オリジナルの実装を呼び出したい場合に使う。

class Foo
  def hoge(x)
    2 * x
  end
end

it do
  o = Foo.new
  # 引数が10の場合200を返すように差し替えて、それ以外はオリジナルの実装を呼び出す
  allow(o).to receive(:hoge).and_call_original
  allow(o).to receive(:hoge).with(10).and_return(200)

  o = Foo.new
  # 引数が10の場合はオリジナルの実装を呼び出して、それ以外は200を返すように差し替え
  allow(o).to receive(:hoge).and_return(200)
  allow(o).to receive(:hoge).with(10).and_call_original
end

and_wrap_original

allowで差し替えるメソッドの呼び出しをラップして、メソッドの実行と終了に割り込みたい場合に使う。

o = Foo.new
allow(o).to receive(:hoge).and_wrap_original do |m, *args|
  # メソッドの呼び出し前に何かしたい場合はここに書く

  m.call(*args) # メソッドの呼び出し

  # メソッドの呼び出し後に何かしたい場合はここに書く
end

ブロックの指定

allow(o).to receive(:hoge) { ... }のようにブロックを指定することができる。ブロックは差し替えたメソッドの実装として使われる。

色々と便利なのでBlock implementationを参照しておくこと。

expect(...).to receive(...)

メソッドが呼び出されることを期待するテスト。呼び出す前に書く必要がある。

class Foo
  def hoge
    piyo
  end

  def piyo
    # ...
  end
end

it do
  object = Foo.new
  expect(object).to receive(:piyo)
  object.hoge # hogeメソッドの中でpiyoメソッドが呼ばれているのでテストをパスする
end

expect(...).to have_received(...)

メソッドが呼び出されることを期待するテスト。呼び出したあとに書く。ただし、allowで差し替えたメソッドかspyで定義したモックでないと利用できない。

object = Foo.new
allow(object).to receive_messages(foo: 1)
object.foo
expect(object).to have_received(:foo)

expect(...).to receive(...).with(...)expect(...).to have_received(...).with(...)

withを使うことで呼び出し時に指定された引数をテストできる。

object = Foo.new
expect(object).to receive(:hoge).with(1, 2)
object.hoge(1, 2)

固定値ではなく任意の値なども指定できる。詳細はMatching argumentsを参照。

as_null_object

オブジェクトをNullObject化させて、すべてのメソッド呼び出しに自身を返すようにする。

d = double('object').as_null_object
# d.eの呼び出しがdを返すのでメソッドチェーンができる
expect(d.e.f.g.h).to eq d

実行回数

onceexactlyなど実行回数を指定することができる。詳細はReceive Countsを参照。

メソッドの実行順序

orderedを使うとメソッドの実行順序を指定できる。詳細はMessage Orderを参照。

定数の差し替え

詳細はMutating constantsを参照。