Skip to content

Monkeypatch raising having no effect at tear down #14161

@santibreo

Description

@santibreo

I think the example is self explanatory

Description

When using monkeypatch fixture to patch and attribute with raising=False, AttributeError is raised at test tear down when attribute is tried to be restituted.

Output of pip list

Package    Version
---------- -------
colorama   0.4.6
iniconfig  2.3.0
packaging  26.0
pip        26.0
pluggy     1.6.0
Pygments   2.19.2
pytest     9.0.2
setuptools 80.10.2
wheel      0.46.3

Pytest and operating system versions

Windows 11 with pytest 9.0.2

Minimal example

import pytest


something_or_none = None


def fake_do_something(*_, **__) -> int:
    return 7


def test_something(monkeypatch: pytest.MonkeyPatch):
    monkeypatch.setattr(
        something_or_none, 'do_something', fake_do_something, raising=False
    )
    assert 7 == something_or_none.do_something('a', 'b', c='c')

When doing pytest <test_file.py> I am getting (paths are masked):

========================================= test session starts =========================================
platform win32 -- Python 3.12.12, pytest-9.0.2, pluggy-1.6.0
rootdir: ...
collected 1 item

...\test_example.py FE                                                                  [100%]

=============================================== ERRORS ================================================
_________________________________ ERROR at teardown of test_something _________________________________

cls = <class '_pytest.runner.CallInfo'>
func = <function call_and_report.<locals>.<lambda> at 0x0000023F84FC5940>, when = 'teardown'
reraise = (<class '_pytest.outcomes.Exit'>, <class 'KeyboardInterrupt'>)

    @classmethod
    def from_call(
        cls,
        func: Callable[[], TResult],
        when: Literal["collect", "setup", "call", "teardown"],
        reraise: type[BaseException] | tuple[type[BaseException], ...] | None = None,
    ) -> CallInfo[TResult]:
        """Call func, wrapping the result in a CallInfo.

        :param func:
            The function to call. Called without arguments.
        :type func: Callable[[], _pytest.runner.TResult]
        :param when:
            The phase in which the function is called.
        :param reraise:
            Exception or exceptions that shall propagate if raised by the
            function, instead of being wrapped in the CallInfo.
        """
        excinfo = None
        instant = timing.Instant()
        try:
>           result: TResult | None = func()
                                     ^^^^^^

..\..\..\...\Lib\site-packages\_pytest\runner.py:353:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
..\..\..\...\Lib\site-packages\_pytest\runner.py:245: in <lambda>
    lambda: runtest_hook(item=item, **kwds),
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
..\..\..\...\Lib\site-packages\pluggy\_hooks.py:512: in __call__
    return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
..\..\..\...\Lib\site-packages\pluggy\_manager.py:120: in _hookexec
    return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
..\..\..\...\Lib\site-packages\_pytest\logging.py:858: in pytest_runtest_teardown
    yield
..\..\..\...\Lib\site-packages\_pytest\capture.py:905: in pytest_runtest_teardown
    return (yield)
            ^^^^^
..\..\..\...\Lib\site-packages\_pytest\runner.py:194: in pytest_runtest_teardown
    item.session._setupstate.teardown_exact(nextitem)
..\..\..\...\Lib\site-packages\_pytest\runner.py:566: in teardown_exact
    raise exceptions[0]
..\..\..\...\Lib\site-packages\_pytest\runner.py:555: in teardown_exact
    fin()
..\..\..\...\Lib\site-packages\_pytest\fixtures.py:1053: in finish
    raise exceptions[0]
..\..\..\...\Lib\site-packages\_pytest\fixtures.py:1042: in finish
    fin()
..\..\..\...\Lib\site-packages\_pytest\fixtures.py:924: in _teardown_yield_fixture
    next(it)
..\..\..\...\Lib\site-packages\_pytest\monkeypatch.py:59: in monkeypatch
    mpatch.undo()
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <_pytest.monkeypatch.MonkeyPatch object at 0x0000023F8683D4C0>

    def undo(self) -> None:
        """Undo previous changes.

        This call consumes the undo stack. Calling it a second time has no
        effect unless you do more monkeypatching after the undo call.

        There is generally no need to call `undo()`, since it is
        called automatically during tear-down.

        .. note::
            The same `monkeypatch` fixture is used across a
            single test function invocation. If `monkeypatch` is used both by
            the test function itself and one of the test fixtures,
            calling `undo()` will undo all of the changes made in
            both functions.

            Prefer to use :meth:`context() <pytest.MonkeyPatch.context>` instead.
        """
        for obj, name, value in reversed(self._setattr):
            if value is not notset:
                setattr(obj, name, value)
            else:
>               delattr(obj, name)
E               AttributeError: 'NoneType' object has no attribute 'do_something'

..\..\..\...\Lib\site-packages\_pytest\monkeypatch.py:416: AttributeError
============================================== FAILURES ===============================================
___________________________________________ test_something ____________________________________________

monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x0000023F8683D4C0>

    def test_something(monkeypatch: pytest.MonkeyPatch):
>       monkeypatch.setattr(
            something_or_none, 'do_something', fake_do_something, raising=False
        )
E       AttributeError: 'NoneType' object has no attribute 'do_something'

...\tests\test_example.py:12: AttributeError
======================================= short test summary info =======================================
FAILED .../tests/test_example.py::test_something - AttributeError: 'NoneType' object has no attribute 'do_something'
ERROR .../tests/test_example.py::test_something - AttributeError: 'NoneType' object has no attribute 'do_something'
===================================== 1 failed, 1 error in 0.22s ======================================

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