We've recently switched from unittest to pytest. I've encountered a strange problem when using mocker.patch
as a context manager. Consider the following example.
module_a.py
class MyClass:
def value(self):
return 10
module_b.py
import module_a
class AnotherClass:
def get_value(self):
return module_a.MyClass().value()
test_module_b.py
from module_b import AnotherClass
def test_main_2(mocker):
with mocker.patch('module_a.MyClass.value', return_value=20):
value = AnotherClass().get_value()
assert value == 20
value = AnotherClass().get_value()
assert value == 10
I would expect that once the context manager exits, MyClass's value method method would be restored (return value of 10), however the test fails on the second assert statement with an assertion error 20 != 10
If I use the exact same test, but replace mocker.patch
with unittest.mock.patch
, it passes. I thought that pytest-mock shared the same API as unittest.mock, so I'm confused as to why there is a difference.
With pytest-mock
, teardown is done when exiting the fixture context. The mocker.patch
object is not just a simple alias for mock.patch
.
You should not need context managers within the test functions when writing pytest-style tests, and in fact the purpose of the pytest-mock plugin is to make the use of context managers and function decorators for mocking unnecessary.
If for some reason you do need a teardown step from within the test itself, then you want the plain old mock API which also works fine within pytest.
from unittest.mock import patch
def test_main_2():
with patch('module_a.MyClass.value', return_value=20):
value = AnotherClass().get_value()
assert value == 20
value = AnotherClass().get_value()
assert value == 10
Be aware that this nested structure is really what pytest intends to avoid, to make your tests more readable, so you're somewhat missing the point if you don't do setup and teardown entirely with fixtures.