mock関数をclassでまとめたいときにpytest.fixture周りで詰まった点に関しての記事です。
ここでは、S3とAPI callの例で説明します。
# サンプルコード
class S3Client:
def __init__(self, bucket: str):
session = boto3.Session()
self._s3_client = session.client("s3")
def sample_func(self, s3_object_key: str) -> bool:
return "output"
def api_call():
return "output"
class TestClass:
def __init__(self, s3_client):
self._s3_client = s3_client
def run(self, api_call):
return api_call.call()
mock関数に書く方法
以前は、mock関数以下のように作成していました。
# mock関数
@pytest.fixture
def mocker_s3_client(mocker):
return mocker.Mock(spec=S3Client)
@pytest.fixture
def mocker_api_call(mocker):
return mocker.Mock(call=mocker.Mock(return_value=None))
# テストコード
def test_A(mocker_s3_client, mocker_api_call):
actual = TestClass(s3_client=mocker_s3_client).run(mocker_api_call)
assert actual == "test"
この場合、
- mockする関数が増えた場合に引数が増える
- テストファイルを分けるときに、importが増える
ため、なるべくclassとしてまとめたいと思いました。
mock関数をclassにまとめる方法
まず、mock関数をMockClass
にまとめます。
# mockクラス
class MockClass:
def __init__(self, mocker):
self._mocker = mocker
def mocker_s3_client(self):
return self._mocker.Mock(spec=S3Client)
def mocker_api_call(self):
return self._mocker.Mock(call=self._mocker.Mock(return_value=None))
この状態で、pytest.fixutreをclassか関数の上にデコレートしようとしましたが、動きませんでした。
うまくいったやり方としては、MockClass
を呼び出す関数を作ることです。
@pytest.fixture
def create_mock_class(mocker):
return MockClass(mocker=mocker)
テストコード側はcreate_mock_class
を呼び出します。
def test_A(create_mock_class: MockClass):
mock_s3_client = create_mock_class.mocker_s3_client()
mock_api_call = create_mock_class.mocker_api_call()
mock_s3_client.sample_func.return_value = True
actual = TestClass(s3_client=mocker_s3_client).run(mocker_api_call)
assert actual == "test"
def test_B(create_mock_class: MockClass):
mock_s3_client = create_mock_class.mocker_s3_client()
mock_api_call = create_mock_class.mocker_api_call()
mock_s3_client.sample_func.return_value = None
actual = TestClass(s3_client=mocker_s3_client).run(mocker_api_call)
assert actual == "test"