Search code examples
pythonunit-testingmockingmagicmock

How to mock a Python class to return unique autospec Mock instance, every time the class is instantiated?


I am writing a program which creates a subprocess.Popen pipeline. I'm trying to mock subprocess.Popen such that each call returns a distinct MagicMock so I can ensure methods are called on specific (or all) processes in the pipeline.

I also want this mock to be autospec'd based on subprocess.Popen but keep getting an error that I can't autospec based on a mock.

Currently my code is as follows:

@pytest.fixture
def Popen(mocker: 'pytest_mock.MockFixture'):
    def popen_factory(*args, **kwargs):
        popen = mocker.MagicMock()  # mocker.create_autospec(subprocess.Popen)
        popen.stdin = open(os.devnull, "wb")
        popen.stdout = open(os.devnull, "rb")
        popen.wait.return_value = 0
        return popen

    Popen = mocker.patch.object(subprocess, 'Popen', autospec=True)
    Popen.side_effect = popen_factory
    yield Popen

Solution

  • Since mocker.patch.object(subprocess, 'Popen', autospec=True) overwrites subprocess.Popen, I have to assign the existing value to a local variable, and use that instead.

    @pytest.fixture
    def Popen(mocker: 'pytest_mock.MockFixture'):
        real_Popen = subprocess.Popen
    
        def popen_factory(*args, **kwargs):
            popen = mocker.create_autospec(real_Popen)
            popen.stdin = open(os.devnull, "wb")
            popen.stdout = open(os.devnull, "rb")
            popen.wait.return_value = 0
            return popen
    
        Popen = mocker.patch.object(subprocess, 'Popen', autospec=True)
        Popen.side_effect = popen_factory
        yield Popen