简体   繁体   中英

Does patch.multiple work with pytest as a decorator

I have a test_tmp.py adopted from https://docs.python.org/3/library/unittest.mock.html#patch-multiple

from unittest.mock import DEFAULT, MagicMock, patch

thing = object()
other = object()

@patch.multiple('__main__', thing=DEFAULT, other=DEFAULT)
def test_function(thing, other):
    print(f'thing={thing}')
    print(f'other={other}')
    assert isinstance(thing, MagicMock)
    assert isinstance(other, MagicMock)

test_function()

It runs with python

python test_tmp.py
thing=<MagicMock name='thing' id='4355085552'>
other=<MagicMock name='other' id='4355243312'>

but it doesn't work with pytest with error like

pytest test_tmp.py
============================================================================================================= test session starts =============================================================================================================
platform darwin -- Python 3.8.2, pytest-5.4.3, py-1.9.0, pluggy-0.13.1
rootdir: /private/tmp
collected 0 items / 1 error

=================================================================================================================== ERRORS ====================================================================================================================
________________________________________________________________________________________________________ ERROR collecting test_tmp.py _________________________________________________________________________________________________________
test_tmp.py:13: in <module>
    test_function()
/Users/user/.pyenv/versions/3.8.2/lib/python3.8/unittest/mock.py:1345: in patched
    with self.decoration_helper(patched,
/Users/user/.pyenv/versions/3.8.2/lib/python3.8/contextlib.py:113: in __enter__
    return next(self.gen)
/Users/user/.pyenv/versions/3.8.2/lib/python3.8/unittest/mock.py:1313: in decoration_helper
    arg = patching.__enter__()
/Users/user/.pyenv/versions/3.8.2/lib/python3.8/unittest/mock.py:1416: in __enter__
    original, local = self.get_original()
/Users/user/.pyenv/versions/3.8.2/lib/python3.8/unittest/mock.py:1389: in get_original
    raise AttributeError(
E   AttributeError: <module '__main__' from '/path/to/bin/pytest'> does not have the attribute 'thing'
=========================================================================================================== short test summary info ===========================================================================================================
ERROR test_tmp.py - AttributeError: <module '__main__' from '/path/to/bin/pytest'> does not have the attribute 'thing'
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
============================================================================================================== 1 error in 0.31s ===============================================================================================================

wondering why?

I'm using pytest = "^5.4.3"

There are a few things here that won't work in pytest:

  • you cannot directly call a test, so calling that function will not work
  • the decorator version doesn't work in this case, I guess pytest just does not implement this (pytest by default understands test function arguments as fixtures; it handles position arguments used by patch and patch.object correctly, but seems not to be able to handle the keyword arguments injected by patch.multiple )
  • using '__main__' may not work, you can use sys.modules[__name__] instead.

So this is a working version:

def test_function1():
    with patch.multiple(sys.modules[__name__],
                        thing=DEFAULT,
                        other=DEFAULT) as mocks:
        print(f'thing = {mocks["thing"]}')
        print(f'other = {mocks["other"]}')
        assert isinstance(thing, MagicMock)
        assert isinstance(other, MagicMock)

This version should also work with unittest .

In pytest, you usually would move this kind of mocking into a fixture, so this is probably more in the pytest spirit:

@pytest.fixture
def multiple():
    with patch.multiple(sys.modules[__name__],
                        thing=DEFAULT,
                        other=DEFAULT) as mocks:
        yield mocks


def test_function(multiple):
    print(f'thing = {multiple["thing"]}')
    print(f'other = {multiple["other"]}')
    assert isinstance(thing, MagicMock)
    assert isinstance(other, MagicMock)

Note:
This does only answer the question in the title, not the "why" - I had a look at the source code of patch.multiple , but didn't understand how it interacts with pytest. Maybe someone with more insight can answer that.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM