モック対象の振る舞いはそのままで、呼び出し回数や引数、返り値のみをテストで確認したい場合に、spyが使える。
TL;DR
今回は、自作のクラスのメソッドがテスト対象だったので、下記のようにspy。
第1引数にオブジェクト、第2引数にメソッド名の文字列リテラル。返り値は、spyのMockオブジェクト。
spy = mocker.spy(app.Hoge, 'bar')
実装コード
def spy(self, obj: object, name: str) -> unittest.mock.MagicMock:
"""
Create a spy of method. It will run method normally, but it is now
possible to use `mock` call features with it, like call count.
:param obj: An object.
:param name: A method in object.
:return: Spy object.
"""
その他メモ
MockerFixtureを使うと、mocker経由で生成したmockなどは、サジェストが有効になるのでちょっと便利。
from pytest_mock import MockerFixture
また最初は、Mock(wraps=)のwrapsオプションでspyのようなことを考えていましたが、spyメソッドを見つけたので、シンプルになりました!spyの実装では、wrapsを活用しているじゃないかと推測してます。下記、実装の一部抜粋。
if asyncio.iscoroutinefunction(method):
wrapped = functools.update_wrapper(async_wrapper, method)
else:
wrapped = functools.update_wrapper(wrapper, method)
spy_obj = self.patch.object(obj, name, side_effect=wrapped, autospec=autospec)
spy_obj.spy_return = None
spy_obj.spy_exception = None
return spy_obj
検証したコード
import pytest
from pytest_mock import MockerFixture
import app
def test_app_spy(mocker: MockerFixture):
spy = mocker.spy(app.Hoge, 'bar')
app.main()
assert spy.call_count == 1
# assert spy.call_count == 2
spy.assert_called_once()
spy.assert_called_once_with(1, 2, 3)
assert spy.spy_return == 1 + 2 + 3
# assert spy.spy_return == 1 + 2 + 4
参考
https://github.com/pytest-dev/pytest-mock#:~:text=not currently tested.-,Spy,-The mocker.spy