Rails minitest キーワード引数を叩くMockの定義方法

[@koshi_life](https://twitter.com/koshi_life) です。
Rails 標準のテストフレームワーク minitest にてキーワード付き引数を叩くMockの定義方法でハマったので備忘です。

### 前提

- Rails 5.2.3
- Ruby 2.6.0
- minitest 5.11.3

## 検証対象モジュール

比較用に検証対象モジュール内で通常引数、キーワード引数各Mock化する２パターンでテストを書きました。
検証対象モジュールは以下とコード参考。

- `Hoge#countup_normal_args()`:  
通常引数の `@api_client.get` , `@api_client.update` を叩く
- `Hoge#countup_keyword_args()`:  
キーワード引数の `@api_client.get(item_id:)`,`@api_client.update(item_id:, value:)` を叩く

```hoge.rb
class Hoge
  def initialize(api_client)
    @api_client = api_client
  end

  # countupメソッド 通常引数のモジュールを利用する
  def countup_normal_args(item_id)
    item = @api_client.get(item_id)
    new_value = item[:val] + 1
    @api_client.update(item_id, new_value)
  end

  # countupメソッド キーワード引数のモジュールを利用する
  def countup_keyword_args(item_id)
    item = @api_client.get(item_id: item_id)
    new_value = item[:val] + 1
    @api_client.update(item_id: item_id, value: new_value)
  end
end
```

## テスト

3つのケースを定義しました。詳細はコード参照。

- 通常引数のメソッドを叩くパターン
- キーワード引数のメソッドを叩くパターン1
- キーワード引数のメソッドを叩くパターン2 Block 利用

```hoge_test.rb
require 'test_helper'

class HogeTest < ActiveSupport::TestCase
  test '#Mock 通常引数のメソッドを叩くパターン' do
    item_id = 'id-1'
    retval_get = { val: 3 }
    args_get = [item_id]

    retval_update = true
    args_update = [item_id, 4]

    api_mock = MiniTest::Mock.new
    api_mock.expect(:get, retval_get, args_get)
    api_mock.expect(:update, retval_update, args_update)

    # 検証したいモジュールを実行します
    hoge = Hoge.new(api_mock)
    result = hoge.countup_normal_args(item_id)
    assert_equal(true, result)

    # Mockに送信されたデータを検証します。
    api_mock.verify
  end

  test 'キーワード引数のメソッドを叩くパターン1' do
    item_id = 'id-2'
    retval_get = { val: 3 }
    args_get = [{ item_id: item_id }] # 配列の中にハッシュで定義

    retval_update = true
    args_update = [{ item_id: item_id, value: 4 }] # 配列の中にハッシュで定義

    api_mock = MiniTest::Mock.new
    api_mock.expect(:get, retval_get, args_get)
    api_mock.expect(:update, retval_update, args_update)

    # 検証したいモジュールを実行します
    hoge = Hoge.new(api_mock)
    result = hoge.countup_keyword_args(item_id)
    assert_equal(true, result)

    # Mockに送信されたデータを検証します。
    api_mock.verify
  end

  test 'キーワード引数のメソッドを叩くパターン2 Block 利用' do
    item_id = 'id-3'
    retval_get = { val: 3 }
    retval_update = true

    api_mock = MiniTest::Mock.new
    api_mock.expect(:get, retval_get) do |item_id:|
      puts ":get() called args item_id:#{item_id}"
      item_id == 'id-3'
    end
    api_mock.expect(:update, retval_update) do |item_id:, value:|
      puts ":update() called args (item_id:#{item_id}, value:#{value})"
      item_id == 'id-3' && value == 4
    end

    # 検証したいモジュールを実行します
    hoge = Hoge.new(api_mock)
    result = hoge.countup_keyword_args(item_id)
    assert_equal(true, result)

    # Mockに送信されたデータを検証します。
    api_mock.verify
  end
end
```

## 参考

- [minitest doc](https://www.rubydoc.info/gems/minitest/5.8.4/Minitest/Mock)
- [StackOverFlow minitest mock expect keyword arguments](https://stackoverflow.com/questions/43494606/minitest-mock-expect-keyword-arguments)
- [Ruby minitest 使い方(具体例あり) - Mock編](https://qiita.com/koshilife/items/dcb1fc7ac0c4a676c17c)