Skip to content

Combining mocker.spy and pytest.MonkeyPatch.context() leaks the patch externally #289

Closed
@kandersolar

Description

@kandersolar

Using mocker.spy on a function patched inside a pytest.MonkeyPatch.context() ends up not undoing the patch on exiting the test function. Here's an example:

import pytest

class SomeClass:

    @staticmethod
    def outer(arg):
        return "outer says (" + SomeClass.inner(arg) + ")"

    @staticmethod
    def inner(arg):
        return "inner says (" + arg + ")"

def test_patch(mocker):
    def patched(arg):
        return "patched says (" + arg + ")"

    with pytest.MonkeyPatch.context() as mp:
        mp.setattr(SomeClass, 'inner', patched)
        #spy = mocker.spy(SomeClass, 'inner')
        assert SomeClass.outer('foo') == 'outer says (patched says (foo))'


def test_normal():
    assert SomeClass.outer('foo') == 'outer says (inner says (foo))'

With the mocker.spy line commented out, both tests pass. Uncommenting it causes the patch to leak out of test_patch and affect test_normal:

    def test_normal():
>       assert SomeClass.outer('foo') == 'outer says (inner says (foo))'
E       AssertionError: assert 'outer says (...d says (foo))' == 'outer says (...r says (foo))'
E         - outer says (inner says (foo))
E         ?             ^^^ ^
E         + outer says (patched says (foo))
E         ?             ^^^^^ ^

test_mod.py:24: AssertionError

I did discover that mocker.stopall() inside the MonkeyPatch context gets things working again, i.e. this seems to work:

    with pytest.MonkeyPatch.context() as mp:
        mp.setattr(SomeClass, 'inner', patched)
        spy = mocker.spy(SomeClass, 'inner')
        assert SomeClass.outer('foo') == 'outer says (patched says (foo))'
        mocker.stopall()

I think I expected all patches and spies to magically undo themselves without me needing to call mocker.stopall() myself. Maybe mixing these magics together is not allowed?

For reference, I'm using pytest 7.1.1, pytest-mock 3.7.0, and python 3.9.12.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions