PythonのMagicMockで"name"をmockする方法

PythonのMagicMockでnameの値をmockしてassertすると失敗するので、その対処方法について。

対処前

以下のようなクラスとテストコードがあり、JobをmockしてPersonのテストを行いたいとする。

class Job:
    def __init__(self, name):
        self.name = name


class Person:
    def __init__(self, job_name):
        self.job = Job(name=job_name)

    def get_job_name(self):
        return self.job.name
from unittest.mock import MagicMock

import pytest
from src.sample import Person


@pytest.fixture(scope="function")
def job_mock():
    return MagicMock(name="my_job")


def test_get_job_name(job_mock):
    person = Person(job_name=job_mock.name)
    assert person.get_job_name() == "my_job"

こんなコードを書くと、以下のようにassertで失敗する。

$ pytest test_sample.py
...
job_mock = <MagicMock name='my_job' id='4330745872'>

    def test_get_job_name(job_mock):
        person = Person(job_name=job_mock.name)
>       assert person.get_job_name() == "my_job"
E       AssertionError: assert <MagicMock name='my_job.name' id='4330800272'> == 'my_job'
E        +  where <MagicMock name='my_job.name' id='4330800272'> = <bound method Person.get_job_name of <src.sample.Person object at 0x101e8e2d0>>()
E        +    where <bound method Person.get_job_name of <src.sample.Person object at 0x101e8e2d0>> = <src.sample.Person object at 0x101e8e2d0>.get_job_name

tests/test_sample.py:14: AssertionError

エラーから、assertの左辺がMagicMockのinstanceになってしまっていることがわかる。

対処方法

この挙動の原因はunittestのドキュメントにある通り、Mockクラスのコンストラクタで指定されたnamemockインスタンスの名前になってしまうため。

これはMockインスタンスの作成後にnameをアトリビュートとしてセットしてあげることで回避できる。

...

@pytest.fixture(scope="function")
def job_mock():
-   return MagicMock(name="my_job")
+   mock = MagicMock()
+   mock.name = "my_job"  # mock.configure_mock(name="my_job")でもOK
+   return mock

...