3
0

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.

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

Posted at

状況

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

原因

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

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

対応

原因がどちらにあるかによって対応は変わる。

  1. テストコードが間違っている場合は、Mockのexpectを増やす
  2. テストコードが正しい場合は、テスト対象のコードのバグを直す

サンプルコード

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がない場合はエラーは起きずにそのままテストメソッドは終わる。

3
0
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
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?