Rails Minitest こんな時どうする？「MockExpectationError: No more expects available for」

# 状況

Rails Minitest Mockを使ったテストコードを実行していたら
「MockExpectationError: No more expects available for」エラーが表示された。

# 原因

Mockのメソッドが想定以上の回数が呼び出されたことが原因。
こうなってしまうのは次の２つのケースが考えられる。

1. テストコードが間違っていて本来用意しておかなければいけないMockのexpectが足りていないケース
2. テストコードは正しくてテスト対象のコードがバグっているケース

# 対応

原因がどちらにあるかによって対応は変わる。
1. テストコードが間違っている場合は、Mockのexpectを増やす
2. テストコードが正しい場合は、テスト対象のコードのバグを直す

# サンプルコード
MyLogic1はAPIクラスを利用して対象者にメッセージを送るクラス。

``` ruby:テスト対象コード
class MyLogic1

  def send_message(api_service, message)
    list = get_members
    list.each do |target|
      api_service.send(target, message)
    end
  end

  def get_members
    ['Iron man', 'Spider man', 'Captain America']
  end

end
```

テストコードはMyLogic1をテストする。APIクラスをMockクラスに置き換えてテストを行う。

``` ruby:テストコード
require 'test_helper'
require 'minitest/autorun'

class MyLogic1Test < ActiveSupport::TestCase

  test '正しくメッセージ送信APIを呼び出しているか' do

    my_logic1 = MyLogic1.new

    mock = MiniTest::Mock.new
    mock.expect(:send, nil, ['Iron man', 'こんにちは！'])

    my_logic1.send_message(mock, 'こんにちは！')

  end

end
```

上記のテストコードを実行すると次のエラーが表示される。
MockExpectationError: No more expects available for :send: ["Spider man", "こんにちは！"]

# テストコードが間違っていた場合

Mockクラスのメソッドが３回呼び出されるのが正しい場合。

``` ruby:テストコード
require 'test_helper'
require 'minitest/autorun'

class MyLogic1Test < ActiveSupport::TestCase

  test '正しくメッセージ送信APIを呼び出しているか' do

    my_logic1 = MyLogic1.new

    mock = MiniTest::Mock.new
    mock.expect(:send, nil, ['Iron man', 'こんにちは！'])
    mock.expect(:send, nil, ['Spider man', 'こんにちは！']) # 追加
    mock.expect(:send, nil, ['Captain America', 'こんにちは！']) # 追加

    my_logic1.send_message(mock, 'こんにちは！')

  end

end
```

# テスト対象コードが間違っていた場合

仮に「Iron man」にだけメッセージを送るという仕様だったとすると、テストコードは正しいのでそのままでテスト対象コードの方を修正する。

``` ruby:テスト対象コード
class MyLogic1

  def send_message(api_service, message)
    list = get_members
    list.each do |target|
      api_service.send(target, message)
    end
  end

  def get_members
    ['Iron man'] # Iron man にだけメッセージを送るという仕様だった
    # ['Iron man', 'Spider man', 'Captain America']
  end

end
```

# 補足

Mockのexpectは足りない場合にエラーを出してくれるが、expectが呼び出されずに残った場合にもエラーを出すことができる。

``` ruby:テスト対象コード
class MyLogic1

  def send_message(api_service, message)
    list = get_members
    list.each do |target|
      api_service.send(target, message)
    end
  end

  def get_members
    ['Iron man', 'Spider man', 'Captain America']
  end

end
```

``` ruby:テストコード
require 'test_helper'
require 'minitest/autorun'

class MyLogic1Test < ActiveSupport::TestCase

  test '正しくメッセージ送信APIを呼び出しているか' do

    my_logic1 = MyLogic1.new

    mock = MiniTest::Mock.new
    mock.expect(:send, nil, ['Iron man', 'こんにちは！'])
    mock.expect(:send, nil, ['Spider man', 'こんにちは！'])
    mock.expect(:send, nil, ['Captain America', 'こんにちは！'])
    mock.expect(:send, nil, ['Bat man', 'こんにちは！']) # 呼ばれない

    my_logic1.send_message(mock, 'こんにちは！')

    mock.verify # ここで残りをチェックする
  end

end
```
このテストコードでは、Bat manにもメッセージを送っていることをテストしたい。
テスト対象コードは、Bat manにはメッセージは送っていない。

最後のmock.verifyを実行した時点で、そのmockで呼び出されていないexpectがあると「MockExpectationError: expected」エラーが出る。

mock.verifyがない場合はエラーは起きずにそのままテストメソッドは終わる。