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

改めて 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を生やしておくことができます。

```rb
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)を返すようになります。

```rb
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が呼び出されたか検査できます。

```rb
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`を利用すると、先に処理を実行して、後から呼び出し検査をおこなうことができます。

```rb
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`を使っておく必要があります。

```rb
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)

```rb
allow(cat).to receive(:name).and_return("タマ")
```
といった書き方で、`cat.name`の戻り値を`"タマ"`にすることができます。
こういった形の設定を一括でやりたい場合は下記のようにできます。

```rb
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に複数の引数を渡しておくと、それを順番に返してくれます。
返し切った場合、それ以降は最後に指定した引数が返されます。

```rb
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`と明確に指定しておけば、元の処理が実行されます。

```rb
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| ... })

引数の値の検査をしたい場合は、次のような形で確認することができます。

```rb
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
```

# 詳しくはこちら
https://relishapp.com/rspec/rspec-mocks/v/3-6/docs/

# 検査用にいろいろ書いてみたRSpec
https://github.com/betachelsea/sample_rspec/tree/master/spec/mocks