How can I mock a function that isn't called by name? - python

Suppose I have a decorator that collects all the functions it decorates to be called at some point in the future.
mydecorator.py
class CallLater(object):
funcs = []
def __init__(self, func):
self.funcs.append(func)
#classmethod
def call_now(cls, *args, **kwargs):
for func in cls.funcs:
func(*args, **kwargs)
Then, I have a function in a module, one of which will be saved by my decorator.
mymodule.py
import logging
from mydecorator import CallLater
logging.basicConfig(level=logging.INFO)
#CallLater
def log_a():
logging.info("A")
#CallLater
def log_b():
logging.info("B")
def log_c():
logging.info("C")
Now, if I import mymodule and call CallLater.call_now(), log_a and log_b will be called. But let's say that during testing, I want log_b to be substituted with log_c. I'll try to do the replacement with a mock.
mock_test.py
import logging
import pytest
from mymodule import log_a, log_c
from mydecorator import CallLater
logging.basicConfig(level=logging.INFO)
pytest_plugins = ('pytest_mock',)
def test_mocking(mocker, caplog):
mocker.patch('mymodule.log_b', log_c)
CallLater.call_now()
logs = [rec.message for rec in caplog.records]
assert logs == ["A", "C"]
But when I run pytest, I see that my mock didn't work.
FAILED mock_test.py::test_mocking - AssertionError: assert ['A', 'B'] == ['A', 'C']
I imagine that 'mymodule.log_b' is the wrong mocking target since it's not being invoked as mymodule.log_b(), but I'm not sure what to use instead in this situation. Any advice is appreciated!

This is not possible as such, the problem being that the functions are already assigned to the list at load time. The only way I can see to patch this would be to patch CallLater.funcs directly, which is a bit awkward, because you have to replace log_b by log_c manually - but here it goes:
import logging
from unittest import mock
from mymodule import log_c
from mydecorator import CallLater
logging.basicConfig(level=logging.INFO)
def test_mocking(caplog):
funcs = [log_c if f.__name__ == 'log_b' else f for f in CallLater.funcs]
with mock.patch.object(CallLater, 'funcs', funcs):
CallLater.call_now()
logs = [rec.message for rec in caplog.records]
assert logs == ["A", "C"]
Note that you cannot compare directly to the function (e.g. f == log_b), because log_b is the decorated function, not the function as saved in CallLater.funcs.

Related

How to mock a function which gets executed during the import time?

Here the ABC() and obj.print_1() get called during the import time and it prints "making object" and "printed 1" respectively. How can we mock all the three functions, __init__(), print_1(), and print_2()?
xyz.py
from abc import ABC
obj = ABC()
obj.print_1()
def func():
return obj.print_2(2)
abc.py
class ABC():
def __init__(self):
print("making object")
def print_1(self):
print("printed 1")
return None
def print_2(self, val):
print("printed ", val)
return None
Indeed, as soon as you import xyz, it will import abc and create an instance then call a method on it.
Solution : import abc yourself BEFORE xyz EVER GETS IMPORTED, and mock the methods defined in the class. And because we can't import a method, patch.object is required.
Note : I added a self as parameter in your ABC.print_1 method, otherwise it would be incorrect. Otherwise make it #staticmethod
Here is the test file I used :
import unittest
import unittest.mock as mock
from so74709409_abc import ABC
# no import of `xyz` here !
class Tests(unittest.TestCase):
def test__xyz_obj_calls_print1(self):
# __init__ must return None
with mock.patch.object(ABC, "__init__", **{"return_value": None}) as mock_init, \
mock.patch.object(ABC, "print_1") as mock_print1, \
mock.patch.object(ABC, "print_2") as mock_print2:
from so74709409_xyz import func # import now !
func()
mock_init.assert_called_once()
mock_print1.assert_called_once_with()
mock_print2.assert_called_once_with(2)
if __name__ == "__main__":
unittest.main()
But this is not very robust, if the module was already imported (maybe indirectly) before the test run, the import inside the test won't have any effect, and so it will fail (mocks not getting called). It can be a pain in a real test suite (with many tests running in sequence) because the previous test will already have imported xyz.
That's why it's better to do these kind of things in a if __name__=="__main__", or in a function called deliberately.
(beware : I assume you choose abc as a dummy name, but it is actually a standard library module for Abstract Base Classes)

Unittest mock: return function which receives one pytest fixture and one argument

I'm creating some unit tests and using pytest fixtures in combination with some unittest.mock patch.object calls.
I would like to reuse a function that is called by some of my tests. It makes use of a pytest fixture (specified as the first "argument" of the function) and it requires an additional argument. It looks something like this:
import pandas as pd
import pytest
import os
from unittest.mock import patch
#pytest.fixture()
def rootdir():
return os.path.dirname(os.path.abspath(__file__))
def my_mock_ret(rootdir, number):
print(f"{rootdir}_{number}")
return f"{rootdir}_{number}"
def test_simple(rootdir):
a = pd.DataFrame()
with patch.object(a, 'to_csv', lambda x: my_mock_ret(rootdir, x)) as _:
a.to_csv(rootdir)
The tricky part is how to pass the number argument to my_mock_ret while also being able to access the rootdir fixture from inside it.
I've tried this way using lambda but it does not work.
Edit
It works if y put my_mock_ret inside test_simple, but I don't want to do that because I want to reuse my_mock_ret for several other tests:
import pandas as pd
import pytest
import os
from unittest.mock import patch
#pytest.fixture()
def rootdir():
return os.path.dirname(os.path.abspath(__file__))
def test_simple(rootdir):
def my_mock_ret(number):
print(f"{rootdir}_{number}")
return f"{rootdir}_{number}"
a = pd.DataFrame()
with patch.object(a, 'to_csv', my_mock_ret) as _:
a.to_csv(rootdir)
What you need right here, is the factory pattern:
import pandas as pd
import pytest
import os
from unittest.mock import patch
#pytest.fixture()
def rootdir():
return os.path.dirname(os.path.abspath(__file__))
#pytest.fixture()
def my_mock_ret(rootdir):
def _my_mock_ret(number):
print(f"{rootdir}_{number}")
return f"{rootdir}_{number}"
return _my_mock_ret
def test_simple(my_mock_ret, rootdir):
a = pd.DataFrame()
with patch.object(a, 'to_csv', my_mock_ret) as _:
a.to_csv(rootdir)
here my_mock_ret will create a function, which captures rootdir, and can take the number argument.

Python: mock import statement of another file

I am in the situation where I have a class that imports two libraries that have the same interface, and in the __init__() decides which one to use depending on some conditions:
# class_file.py
import lib1
import lib22
class A:
def __init__(self, condition):
if condition:
self.attr = lib1
else:
self.attr = lib2
Since both lib1 and lib2 cannot be used in testing, I need to mock them. But I do not want to fully replace the __init__(), I just want that when A.__init__() will be called, the lines self.attr = lib1 or self.attr = lib2 will use a mocked version of lib1 and lib2:
# test.py
from class_file import A
def get_mocked_a():
# How to substitute lib1 and lib2 in class_file.py so that the following
# line will return an object where self.attr is mocked?
return A()
def test():
a = get_mocked_a()
How can I achieve this? I need get_mocked_a() to be a function, because A is pretty complex to instantiate, therefore it's much cleaner to obtain an instance of A in test functions by calling it.
If you want to mock some functionality direct in your test cases then try this.
from unittest import mock
#inside your test
def test():
with mock.patch('your_function_of_choosen_library', return_value='what_you_want_to_return'):
#your code
Edited:
from class_file import A
def get_mocked_a():
with mock.patch('your_function_of_choosen_library', return_value='A'):
return A
def test():
a = get_mocked_a()

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.

Python: patching function defined in same module of tested function

I have been working with Python's unittest.mock library quite a bit, but right now I'm struggling with a use case that may not be approached correctly.
Consider a file mymodule/code.py containing the following snippet:
def sum():
pass
def mul():
pass
def div():
pass
def get_functions():
return [sum, mul, div]
def foo():
functions = get_functions()
for func in functions:
func()
I want to test the foo function, patching the sum function, and leaving mul and div as they are. This is what I tried initially:
class TestFoo(unittest.TestCase):
#mock.patch('mymodule.code.foo.sum')
def test_foo(foo_sum_mock):
foo()
foo_sum_mock.assert_called_once()
However, the patching approach illustrated above does not work. I believe that the sum function is patched correctly when loading mymodule.code.py, but redefined due to the def sum() block.
By reading the official documentation, I also tried to use the start and stop functions of the unittest.mock library as follows:
def test_foo():
patcher = mock.patch('module.code.sum')
mocked_sum_fun = patcher.start()
foo()
mocked_sum_fun.assert_called_once()
mock_sum_fun.stop()
This approach also did not work. I was hoping it would avoid the sum function override after the modules/code.py file gets loaded.
Is it possible to patch a local function such as sum? Or is moving the sum function to another file the only option for patching?
Many thanks in advance!
You can mock a function of same module using mock.patch and refering this module as __main__
code.py
from unittest.mock import patch
def sum():
print("called method sum")
pass
def call_sum():
sum()
def return_mock():
print("I'm a mocked method")
return True
with patch('__main__.sum', return_value=return_mock()) as mock_test:
call_sum()
mock_test.assert_called_once() # assure that mocked method was called, not original.
You could also use the path of lib (my_project.code.sum) instead of __main__.sum.
Generally speaking, you'd want to separate your test code from your production code:
code.py
def sum():
pass
def mul():
pass
def div():
pass
def get_functions():
return [sum, mul, div]
def foo():
functions = get_functions()
for func in functions:
func()
code_test.py
import unittest
import mock_test as mock
import code
class TestFoo(unittest.TestCase):
#mock.patch('code.sum')
def test_foo(self, sum_mock):
def new_sum_mock(*args, **kwargs):
# mock code here
pass
sum_mock.side_effect = new_sum_mock
code.foo()
sum_mock.assert_called_once()
But yes, you could place it all into one file:
code_test.py:
import unittest
import mock_test as mock
import code
def sum():
pass
def mul():
pass
def div():
pass
def get_functions():
return [sum, mul, div]
def foo():
functions = get_functions()
for func in functions:
func()
class TestFoo(unittest.TestCase):
#mock.patch('code_test.sum')
def test_foo(self, sum_mock):
def new_sum_mock(*args, **kwargs):
# mock code here
pass
sum_mock.side_effect = new_sum_mock
code.foo()
sum_mock.assert_called_once()

Categories