😎

[Python] unittest.mock チートシート

2024/12/04に公開

Python の unittest.mock に関するチートシート。

※あくまで自分用のチートシートなので、テストコードはかなり簡潔に書いています。

公式のドキュメントはこちら
https://docs.python.org/ja/3/library/unittest.mock.html

unittest.mock.MagicMock

特定のクラスなどをモックしたい場合に利用。

# これは実装側
class Foo:
    # クラスのプロパティ
    is_foo = True

    # クラスのメソッド
    def do_something():
        # ...

# これはモック定義
import pytest
from unittest.mock import MagicMock, patch
from path.to.foo import Foo

@pytest.fixture()
def mock_foo():
    foo_mock = MagicMock(spec=Foo)

    foo_mock.do_something = MagicMock(return_value=123)

    with patch('path.to.foo.Foo', return_value=foo_mock):
        # 各テストケースはこの with の中で実行される
        yield foo_mock  # テストケースからモックしたインスタンスを参照するため、yeild に foo_mock を渡す

# これはテストファイル
class TestFoo:
    def test(self, mock_foo):
        # Fooクラスがモックされた状態でのテストとなる
        
        assert mock_foo.do_something.call_count == 1

unittest.mock.patch

クラスの特定のメソッドなどをモックしたい場合に利用。

from unittest.mock import patch
from path.to.foo import Foo

class TestFoo:
    # patch は何度でも指定できる
    # patch ではなく patch.object を使うことで、参照元にコードジャンプしやすくなる
    @patch.object(Foo, 'do_something')
    @patch.object(Foo, 'check_if_ok')
    def test(
        # 引数の順序は、デコレータで指定した順序の逆になる(Pythonの言語仕様)
        mock_check_if_ok,
        mock_do_something,
    ):
        # 例外を投げさせる
        mock_check_if_ok.side_effect = Exception('NG!')

        # 返り値を設定する
        mock_do_something.return_value = True

        # 1度も実行されていない
        assert mock_do_something.assert_not_called()

        mock_do_something(1,2,3)

        # 実行された
        assert mock_do_something.called

        # 少なくとも1度実行された
        assert mock_do_something.assert_called()

        # 1度だけ実行された
        assert mock_do_something.assert_called_once()

        # 1度だけ実行された(引数指定)
        assert mock_do_something.assert_called_once_with(1,2,3)

        # 最後に実行された際の引数
        args = mock_do_something.call_args.args
        assert args[0] == 1
        
        mock_do_something(key='abc', value=100)

        # N回実行された
        assert mock_do_something.call_count = 2

        # 最後に実行された際のキーワード引数
        kargs = mock_do_something.call_args.kargs
        assert kargs['key'] == 'abc'

        # 今までに以下のように実行されたことがある
        assert mock_do_something.assert_any_call(1,2,3)

        # これまでの実行された引数
        assert mock_do_something.call_args_list == ...

以下、個人的な所感。

  • 自前で MagicMock() するとモックを元に戻す手間が発生するので、ちょっとしたモックでは patch デコレータを使うのが良さそう
  • assert_called() よりは called の方がシンプルで良い
  • call_args には最後に実行された引数が入っている、と覚えておく
  • assert_any_call() は便利だけど、名称に any が入っているのでちょっと拒否感がある
  • assert_has_calls() は使うことはあまり無さそう… 実装を変えたらすぐにテストが壊れそう any_order=True を指定しても良いけど
  • call オブジェクトとか気にしたくないので、それを意識させないテストコードにするのが良さそう

Discussion