改めて RSpec Mocks のドキュメントを拾い読みして、改めて勉強になったところをまとめました。
基本的なところは飛ばしています。
$ rspec -v
RSpec 3.6
- rspec-core 3.6.0
- rspec-expectations 3.6.0
- rspec-mocks 3.6.0
- rspec-support 3.6.0
モックオブジェクトを使用する(double)
モックオブジェクトを double
で定義することができます。
定義する時にまとめて呼び出し予定のmethodを生やしておくことができます。
describe "Test Doubles" do
let(:item) { double("item", foo: 3, bar: 4) }
it '定義methodが値を返すこと' do
expect(item.foo).to eq(3)
expect(item.bar).to eq(4)
end
it '定義されていないmethodは呼び出せないこと' do
expect{ item.hoge }.to raise_error(/received unexpected message/)
end
end
item.hoge
など未設定の呼び出しをすると例外が発生してしまうが、
.as_null_object
を使うと自分自身(double)を返すようになります。
describe "Null object doubles" do
let(:item) { double("item", foo: 3, bar: 4).as_null_object }
it '定義されていないmethodはdouble自身を返すこと' do
expect(item.hoge).to eq(item)
expect(item.hoge.fuga.piyo).to eq(item)
end
end
実行後に対象のmethodが呼び出されたか検査する(spy)
expect~receive
を利用すると、それ以降の実行で対象のmethodが呼び出されたか検査できます。
describe "Expecting messages" do
let(:cat) { Cat.new }
it "nameが呼び出されること" do
expect(cat).to receive(:name)
cat.name # 呼び出しがなければテスト失敗
end
end
ただし上記の例はcat.name
とexpect...
の行をひっくり返すと、expect以降にcat.name
が呼び出されないため、テストが失敗してしまいます。
spy
を利用すると、先に処理を実行して、後から呼び出し検査をおこなうことができます。
describe "Spies" do
let(:dog) { spy('dog') }
it '呼び出したことの確認' do
dog.name
expect(dog).to have_received(:name)
end
it '引数の確認' do
dog.eat('tomato')
expect(dog).to have_received(:eat).with('tomato')
end
it '呼び出し順の確認' do
dog.eat('tomato')
dog.eat('apple')
expect(dog).to have_received(:eat).with('tomato').ordered
expect(dog).to have_received(:eat).with('apple').ordered
end
end
spy
が利用できない場合は、allow...receive
を使っておく必要があります。
context "任意のclassを使った場合" do
let(:cat) { Cat.new }
it "呼び出したことの確認ができること" do
allow(cat).to receive(:name) # allowで指定しておかないと追いかけてくれない
cat.name
expect(cat).to have_received(:name)
end
end
オブジェクトの複数methodに任意の戻り値を一括設定する(receive_messages)
allow(cat).to receive(:name).and_return("タマ")
といった書き方で、cat.name
の戻り値を"タマ"
にすることができます。
こういった形の設定を一括でやりたい場合は下記のようにできます。
describe "Allowing messages" do
let(:cat) { Cat.new }
it "戻り値を設定できること(複数項目)" do
allow(cat).to receive_messages(name: "タマ", age: 1)
expect(cat.name).to eq("タマ")
expect(cat.age).to eq(1)
end
end
呼ばれる回数に応じて戻り値を切り替える(and_return(x, y, z))
methodが複数回呼ばれる場合、and_returnに複数の引数を渡しておくと、それを順番に返してくれます。
返し切った場合、それ以降は最後に指定した引数が返されます。
describe "Returning a value" do
let(:item) { double("item") }
it "呼び出す回数に応じた戻り値を設定できること" do
allow(item).to receive(:foo).and_return(1, 2, 3)
expect(item.foo).to eq(1)
expect(item.foo).to eq(2)
expect(item.foo).to eq(3)
expect(item.foo).to eq(3)
end
end
method呼び出し検査をするが、対象methodの元の挙動も実行する(and_call_original)
expect...receive
を用いてmethod呼び出し検査をすると、対象methodが呼び出されることは確認できますが、実行がされないためテストに影響を与える場合があります。
その際はand_call_original
と明確に指定しておけば、元の処理が実行されます。
class Calculator
def self.add(x, y)
x + y
end
def self.sum(array)
result = 0
array.each { |n| result = self.add(result, n) }
result
end
end
describe "Calling the original implementation" do
it { expect(Calculator.sum([1, 2, 3])).to eq(6) }
it "expectで呼び出し確認をすると実行されなくなる" do
expect(Calculator).to receive(:add).exactly(3)
expect(Calculator.sum([1, 2, 3])).to eq(nil)
end
it "expect...and_call_original と指定すれば実行される" do
expect(Calculator).to receive(:add).exactly(3).and_call_original
expect(Calculator.sum([1, 2, 3])).to eq(6)
end
end
どんな引数を渡されたかを検査する(receive{|arg| ... })
引数の値の検査をしたい場合は、次のような形で確認することができます。
describe "Use a block to verify arguments" do
it "addメソッドが引数1,2で呼び出されていることを確認" do
allow(Calculator).to receive(:add) do |x, y|
expect(x).to eq(1)
expect(y).to eq(2)
end
Calculator.add(1, 2)
end
end