pytest monkeypatch class method while calling super - python

I have a module loaders with class "Loader" with class method "load". During test, I want to append some additional steps to "Loader.load" to account for test specific data post-processing, so essentially overriding it. How do I properly do that?
I tried creating a mock class which inherits Loader and use monkeypatch.setattr("loaders.Loader", mock_loader), but this only works when I run one single test, but not when I run all tests.
loaders.py
class Loader:
def load():
# do something
return data
test.py
from loaders import Loader
class MockLoader(Loader):
def load():
data = super().load()
# do something to data
return data
def test_loader_special1(monkeypatch):
monkeypatch.setattr("loaders.Loader", MockLoader)
#run test logic 1
def test_loader_special2(monkeypatch):
monkeypatch.setattr("loaders.Loader", MockLoader)
#run test logic 2

Use patch or patch.object either via the builtin library unittest or the external library pytest-mock :
patch() acts as a function decorator, class decorator or a context
manager. Inside the body of the function or with statement, the target
is patched with a new object.
Where it is explicitly documented that the patch is only applicable per test:
When the function/with statement exits the patch is undone.
The patch will be used to wrap around your real implementation so that you can perform any necessary steps before and/or after calling it.
loaders.py
class Loader:
def load(self):
print("Real load() called")
return "real"
test_loaders.py
import pytest
from unittest.mock import patch
from loaders import Loader
#pytest.fixture
def mock_load(mocker):
real_func = Loader.load
def mock_func(self, *args, **kwargs):
print("Mock load() called")
data = real_func(self, *args, **kwargs)
data += " and mock"
return data
# Option 1: Using pytest-mock + new
mocker.patch.object(Loader, 'load', new=mock_func)
"""
Alternative ways of doing Option 1. All would just work the same.
# Option 2: Using pytest-mock + side_effect
mocker.patch.object(Loader, 'load', side_effect=mock_func, autospec=True)
# Option 3: Using unittest + new
with patch.object(Loader, 'load', new=mock_func):
yield
# Option 4: Using unittest + new
with patch.object(Loader, 'load', side_effect=mock_func, autospec=True):
yield
"""
def test_loader_special1(mock_load):
data = Loader().load()
print(f"{data=}")
assert data == "real and mock"
def test_loader_special2(mock_load):
data = Loader().load()
print(f"{data=}")
assert data == "real and mock"
def test_loader_special3():
data = Loader().load()
print(f"{data=}")
assert data == "real"
def test_loader_special4(mock_load):
data = Loader().load()
print(f"{data=}")
assert data == "real and mock"
Output:
$ pytest test_loaders.py -q -rP
.... [100%]
================================================================================================= PASSES ==================================================================================================
__________________________________________________________________________________________ test_loader_special1 ___________________________________________________________________________________________
------------------------------------------------------------------------------------------ Captured stdout call -------------------------------------------------------------------------------------------
Mock load() called
Real load() called
data='real and mock'
__________________________________________________________________________________________ test_loader_special2 ___________________________________________________________________________________________
------------------------------------------------------------------------------------------ Captured stdout call -------------------------------------------------------------------------------------------
Mock load() called
Real load() called
data='real and mock'
__________________________________________________________________________________________ test_loader_special3 ___________________________________________________________________________________________
------------------------------------------------------------------------------------------ Captured stdout call -------------------------------------------------------------------------------------------
Real load() called
data='real'
__________________________________________________________________________________________ test_loader_special4 ___________________________________________________________________________________________
------------------------------------------------------------------------------------------ Captured stdout call -------------------------------------------------------------------------------------------
Mock load() called
Real load() called
data='real and mock'
4 passed in 0.01s

Modifying the answer https://stackoverflow.com/a/68675071/14536215 to use monkeypatch:
#pytest.fixture
def mock_load(monkeypatch):
real_func = Loader.load
def mock_func(self, *args, **kwargs):
print("Mock load() called")
data = real_func(self, *args, **kwargs)
data += " and mock"
return data
monkeypatch.setattr(Loader, "load", mock_func)
You can then mark the fixture to be loaded for all tests in the testing module with:
#pytest.fixture(autouse=True)
or mark the tests to use specified fixtures with:
#pytest.mark.usefixtures("mock_load")
def test_loader_special1():
...
Edit:
If you want to mock the whole class, you need to mock it before you import the class for it to take effect, or you could just import the module so you don't have the import statements sprinkled around:
import loaders
class MockLoader(loaders.Loader):
def load(self):
data = super().load()
data += " and mock"
return data
def test_loader_special1(monkeypatch):
monkeypatch.setattr("loaders.Loader", MockLoader)
data = loaders.Loader().load()
print(f"{data=}")
assert data == "real and mock"

Related

Python: is it possible to wrap "#patch(path)" for re-use? (unittest)

As the doc "Where to patch" says, we need to patch where an object is looked up, (not where it's defined); so I understand that it's not possible to - let's say - create a reusable patch for a particular path
Imagine you have several modules importing an object you'd like to mock
# file_a.py
from foo.goo.hoo import settings
# file_b.py
from foo.goo.hoo import settings
# file_c.py
from foo.goo.hoo import settings
I was wondering if there is a way to create a decorator such as:
#mock_settings
def test_whatever(self, settings_mock):
...
instead of this solution:
#patch("some_module.file_a.settings")
def test_whatever(self, settings_mock):
...
#patch("some_module.file_b.settings")
def test_whatever(self, settings_mock):
...
#patch("some_module.file_c.settings")
def test_whatever(self, settings_mock):
...
As mentioned in the question, to patch an object you have to patch its reference in the module to be tested (in case it is imported using from ...import).
To have it patched in several modules, you can patch all of these modules with the same mock, and use that mock. If you know in advance which modules you want to patch, you can just do this. If you don't know them in advance, you have to try to patch the object in all loaded modules -- this may get a bit more complicated.
I will show an example using pytest and a pytest fixture, as this is more compact; you could wrap that in a decorator for usage in unittest, but that will not change the basics. Consider we have a class that needs to be mocked in several modules:
class_to_mock.py
class ClassToMock:
def foo(self, msg):
return msg
module1.py
from class_to_mock import ClassToMock
def do_something():
inst = ClassToMock()
return inst.foo("module1")
module2.py
from class_to_mock import ClassToMock
def do_something_else():
inst = ClassToMock()
return inst.foo("module2")
You can now write a fixture that mocks the class in all of these modules at once (here using pytest-mock for simplicity):
#pytest.fixture
def mocked_class(mocker):
mocked = Mock()
for module in ('module1', 'module2'):
mocker.patch(module + '.ClassToMock', mocked)
yield mocked
This can be used to test both modules:
def test_module1(mocked_class):
mocked_class.return_value.foo.return_value = 'mocked!'
assert module1.do_something() == 'mocked!'
def test_module2(mocked_class):
mocked_class.return_value.foo.return_value = 'mocked!'
assert module2.do_something_else() == 'mocked!'
If you want a generic version that mocks the class in all loaded modules, you can replace the fixture with something like this:
#pytest.fixture
def mocked_class(mocker):
mocked = Mock()
for name, module in list(sys.modules.items()):
if not inspect.ismodule(module):
continue
for cls_name, cls in module.__dict__.items():
try: # need that as inspect may raise for some modules
if inspect.isclass(cls) and cls_name == "ClassToMock":
mocker.patch(name + ".ClassToMock", mocked)
except Exception:
continue
yield mocked
This will work for this specific example - to generalize this, it has to consider more object types, the class shall be configurable, and there may be some more issues - opposed to the more simple version where you just enumerate the modules you want to patch, which will always work.
You could do something similar in unittest.setUp by putting the mock in an instance variable, though that is less elegant, because you are also responsible for stopping the mocking:
class ModulesTest(unittest.TestCase):
def setUp(self):
self.mocked_class = Mock()
self.mocks = []
for module in ('module1', 'module2'):
mocked = mock.patch(module + '.ClassToMock', self.mocked_class)
self.mocks.append(mocked)
mocked.start()
def tearDown(self):
for mocked in self.mocks:
mocked.stop()
def test_module1(self):
self.mocked_class.return_value.foo.return_value = 'mocked!'
assert module1.do_something() == 'mocked!'
And you can also wrap this in a decorator, to answer your original question at least partially:
def mocked_class_to_mock(f):
#wraps(f)
def _mocked_class_to_mock(*args, **kwargs):
mocked_class = Mock()
mocks = []
for module in ('module1', 'module2'):
mocked = mock.patch(module + '.ClassToMock', mocked_class)
mocks.append(mocked)
mocked.start()
kwargs['mocked_class'] = mocked_class # use a keyword arg for simplicity
f(*args, **kwargs)
for mocked in mocks:
mocked.stop()
return _mocked_class_to_mock
...
#mocked_class_to_mock
def test_module3(self, mocked_class):
mocked_class.return_value.foo.return_value = 'mocked!'
assert module3.do_something() == 'mocked!'
Of course, you can do the same with the more generic version, if needed.
Also note that I skipped the simpler case where the object is imported using import .... In this case, you have to patch the original module. In the generic fixture, you probably want to add that case always.

Using pytest fixture from common test function

I have some unittest-based code that currently looks like this:
class TestMain(TestCase):
def run_prog(self, args):
with TemporaryFile() as stdout:
old_stdout = sys.stdout
try:
main.main()
stdout.seek(0)
stdout_data = stdout.read()
finally:
sys.stdout = old_stdout
return stdout_data
def test_one(self):
out = self.run_prog(...)
def test_two(self):
out = self.run_prog(...)
def test_three(self):
out = self.run_prog(...)
run_prog invokes the "main" program under test and manually captures its stdout.
I'm in the process of converting this project to pytest, but this last piece has pushed the limits of my understanding of pytest fixtures.
I understand that pytest has full support for capturing stdout/stderr and I would like to leverage this.
The problem is, their examples work on a test-function level:
def test_myoutput(capfd):
do_something
captured = capsys.readouterr()
assert captured.out == "hello\n"
assert captured.err == "world\n"
In my case, run_prog is used 42 times, so I'm trying to use the fixture starting at run_prog -- the calling functions ideally don't need to bother with capsys/capfd.
Is there a way to "invoke" the fixture from my run_prog helper? Or do I need to add capfd to all 42 tests and pass it to run_prog?
You can define an autouse fixture that will store the CaptureFixture object (returned by the capsys fixture) as an instance property:
class TestMain(TestCase):
#pytest.fixture(autouse=True)
def inject_capsys(self, capsys):
self._capsys = capsys
def run_prog(self, args):
main.main()
return self._capsys.out
def test_out(self):
assert self.run_prog('spam') == 'eggs'
The TestMain.inject_capsys fixture will be rerun for each test, guaranteeing the test isolation (no output from test_one will be leaked in test_two etc).
Here's a slight variation on hoefling's answer that gives a little more control over the scope of the capsys fixture.
It uses request.getfixturevalue() to retrieve the fixture at function invocation time:
import pytest
import sys
class TestMain:
#pytest.fixture(autouse=True)
def inject_request(self, request):
self.request = request
def run_prog(self, message):
capfd = self.request.getfixturevalue('capfd')
sys.stdout.write(message)
captured = capfd.readouterr()
assert captured.out == message
def test_one(self):
self.run_prog("Hello world!")

How to mock a function in another method being called using pytest

I have the following structure for the project
myapp
|
-contrib
|
- helpers.py
- views
|
- app_view.py
In helpers.py I have the following method
def method(**kwargs) -> int:
# Some code here
requests.post(
url=f"{REMOTE_SERVER}/sc",
json=kwargs,
timeout=5
)
In app_view.py I have the following implementation. I am calling the method inside a thread and I need to mock it using pytest to check whether it's being called once and called with certain number of parameters.
import myapp.contrib.helpers import method
class Score(MethodView):
#prepare_req
def post(self, **kwargs):
"""
Post Score
"""
response_data = dict()
# Some logic here
self.filter_score(kwargs)
return jsonify(response_data)
def filter_score(self, **kwargs)
thread = threading.Thread(target=method, kwargs=event_data)
thread.start()
Using pytest I am trying to mock the method() as follows.
def test_score(client, mocker):
url = "sc/1.1/score"
patched_method = mocker.patch(
"myapp.contrib.helpers.method"
)
client.post(url, json=issue_voucher_post_data)
patched_method.assert_called_once() # <- this is getting false and not mocking the method
Tried the below as well but it wont patch properly
patched_method = mocker.patch(
"myapp.views.app_view.method"
)
But it will call the corresponding view
How to mock a function used inside a thread in a view which is available in another module using pytest
?

Python mock a method when specific argument

I have a python method like
import external_object
from external_lib1 import ExternalClass1
from external_lib2 import Hook
class MyClass(self):
def my_method(self):
ExternalClass.get('arg1') #should be mocked and return a specific value with this arg1
ExternalClass.get('arg2') #should be mocked and return a specific value with this arg2
def get_hook(self):
return Hook() # return a mock object with mocked method on it
def my_method(self):
object_1 = external_object.instance_type_1('args') # those are two different object instanciate from the same lib.
object_2 = external_object.instance_type_2('args')
object_1.method_1('arg') # should return what I want when object_1 mocked
object_2.method_2 ('arg') # should return what I want when object_2 mocked
In my test I would like to realise what I put in comments.
I could manage to do it, but every time it gets really messy.
I use to call flexmock for some stuff (by example ExternalClass.get('arg1') would be mock with a flexmock(ExternalClass).should_return('arg').with_args('arg') # etc...) but I'm tired of using different test libs to mock.
I would like to use only the mock library but I struggle to find a consistent way of doing it.
I like to use python's unittest lib. Concretely the unittest.mock which is a great lib to customize side effects and return value in unit tested functions.
They can be used as follows:
class Some(object):
"""
You want to test this class
external_lib is an external component we cannot test
"""
def __init__(self, external_lib):
self.lib = external_lib
def create_index(self, unique_index):
"""
Create an index.
"""
try:
self.lib.create(index=unique_index) # mock this
return True
except MyException as e:
self.logger.error(e.__dict__, color="red")
return False
class MockLib():
pass
class TestSome(unittest.TestCase):
def setUp(self):
self.lib = MockLib()
self.some = Some(self.lib)
def test_create_index(self):
# This will test the method returns True if everything went fine
self.some.create_index = MagicMock(return_value={})
self.assertTrue(self.some.create_index("test-index"))
def test_create_index_fail(self):
# This will test the exception is handled and return False
self.some.create_index = MagicMock(side_effect=MyException("error create"))
self.assertFalse(self.some.create_index("test-index"))
Put the TestSome() class file somewhere like your-codebase-path/tests and run:
python -m unittest -v
I hope it's useful.

how to pass magic mocked references to main method in python

I've tried to stub out classes constructed in the main function so that I can test against main and assert that classes are initialized with specific data. However main function still does not pick up the mocked instances. How can I pass along the mocked instance to main.
from unittest.mock import patch
from contextlib import contextmanager
#contextmanager
def use_mocked(method, cls, ret_value):
class MockedClass(cls):
pass
def func(cls):
return ret_value
def fullname(o):
return o.__module__ + "." + o.__name__
setattr(MockedClass, method, classmethod(func))
with patch(fullname(cls), MockedClass):
yield
This is the patching utility to make sure main is passed the mocked reference. I may be confused on my understanding of how its functioning.
def test_main():
magic_b = MagicMock(spec_set=Benchmark, wraps=Benchmark)
with use_mocked("__new__", DataStream, magic_b):
main.main()
magic_b.assert_called_once_with() # fails
in the main module, I have a main method defined as...
import benchmark.Benchmark
def main():
b = benchmark.Benchmark() # <- this is not the mocked instance
...
I relied on the same patch utility in unittest.mock but instead just used it in the form of a decorator around my test. patch() now is passed in the Benchmark class which main imports (it is important to patch in main and not in benchmark modules it self ie. don't patch benchmark.Benchmark). Main module remains untouched and tests now pass.
import main
#patch("main.Benchmark")
# b here is a ref to MagicMock class mocking Benchmark;
# it is substituted into the execution of main module,
# patch provides it as a param so you can assert against it.
def test_main(b):
main.main()
b.assert_called_once_with()

Categories