状況
Rails Minitest Mockを使ったテストコードを実行していたら
「MockExpectationError: No more expects available for」エラーが表示された。
原因
Mockのメソッドが想定以上の回数が呼び出されたことが原因。
こうなってしまうのは次の2つのケースが考えられる。
- テストコードが間違っていて本来用意しておかなければいけないMockのexpectが足りていないケース
- テストコードは正しくてテスト対象のコードがバグっているケース
対応
原因がどちらにあるかによって対応は変わる。
- テストコードが間違っている場合は、Mockのexpectを増やす
- テストコードが正しい場合は、テスト対象のコードのバグを直す
サンプルコード
MyLogic1はAPIクラスを利用して対象者にメッセージを送るクラス。
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クラスに置き換えてテストを行う。
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クラスのメソッドが3回呼び出されるのが正しい場合。
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」にだけメッセージを送るという仕様だったとすると、テストコードは正しいのでそのままでテスト対象コードの方を修正する。
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が呼び出されずに残った場合にもエラーを出すことができる。
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
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がない場合はエラーは起きずにそのままテストメソッドは終わる。