38
23

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

RSpec Mocks ドキュメント拾い読み

Posted at

改めて 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.nameexpect...の行をひっくり返すと、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

詳しくはこちら

検査用にいろいろ書いてみたRSpec

38
23
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
38
23

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?