Python Unit Test Patch Function - Avoid passing mocked function to test function - python

I am trying to mock a python function similar to below. I am not doing anything with the mocked function except for the fact that it is used to return the mocked data in the called function. Is it possible for me to avoid passing in the variable (sum, in this case) to the test function?
# test_calculator.py
from unittest import TestCase
from unittest.mock import patch
class TestCalculator(TestCase):
#patch('calculator.Calculator.sum', return_value=9)
def test_sum(self, sum):
self.assertEqual(sum(2,3), 9)

unittest.mock.patch can also be used as a context manager, if simply avoiding sum in the parameters is desired
class TestCalculator(TestCase):
def test_sum(self):
with patch('calculator.Calculator.sum', return_value=9) as sum:
self.assertEqual(sum(2, 3), 9)

Related

How does a pytest mocker work given there is no import statement for it?

I am following this mini-tutorial/blog on pytest-mock. I can not understand how the mocker is working since there is no import for it - in particular the function declaration def test_mocking_constant_a(mocker):
import mock_examples.functions
from mock_examples.functions import double
def test_mocking_constant_a(mocker):
mocker.patch.object(mock_examples.functions, 'CONSTANT_A', 2)
expected = 4
actual = double() # now it returns 4, not 2
assert expected == actual
Somehow the mocker has the attributes/functions of pytest-mocker.mocker: in particular mocker.patch.object . But how can that be without the import statement?
The mocker variable is a Pytest fixture. Rather than using imports, fixtures are supplied using dependency injection - that is, Pytest takes care of creating the mocker object for you and supplies it to the test function when it runs the test.
Pytest-mock defines the "mocker" fixture here, using the Pytest fixture decorator. Here, the fixture decorator is used as a regular function, which is a slightly unusual way of doing it. A more typical way of using the fixture decorator would look something like this:
#pytest.fixture()
def mocker(pytestconfig: Any) -> Generator[MockerFixture, None, None]:
"""
Return an object that has the same interface to the `mock` module, but
takes care of automatically undoing all patches after each test method.
"""
result = MockerFixture(pytestconfig)
yield result
result.stopall()
The fixture decorator registers the "mocker" function with Pytest, and when Pytest runs a test with a parameter called "mocker", it inserts the result of the "mocker" function for you.
Pytest can do this because it uses Python's introspection features to view the list of arguments, complete with names, before calling the test function. It compares the names of the arguments with names of fixtures that have been registered, and if the names match, it supplies the corresponding object to that parameter of the test function.

Patching and unpatching object method in python

I have a class with multiple methods. One of these methods use a seed and pseudorandom numbers (using RandomState, to allow seeds to be independent). Let's call it method GEN_RND().
The problem is when trying to create a unittest for a method that uses GEN_RND (let's call it MY_METHOD()).
I'd like to temporarily mock GEN_RND, just for this one test, and then "unpatch" it, for the rest of the tests.
Any idea how to do that?
import unittest
from unittest.mock import patch
def mock_GEN_RND(x, y):
return 3
class TestMyObj(unittest.TestCase):
obj_instance = MyObj()
#patch.object(MyObj, 'GEN_RND', mock_GEN_RND)
def test_MY_METHOD(self):
x = obj_instance.MY_METHOD(0.3)
self.assertEqual(x, 3) # test uses mock and should pass
# How to "un-patch" from here to avoid inadvertently using mock_GEN_RND
# in subsequent tests?
Accepted answer by Klaus D.
The mock exists only within the scope of the decorated method.

How do I mock the default arguments of a function that is used by the function I'm testing?

I have this my_module.py:
def _sub_function(do_the_thing=True):
if do_the_thing:
do_stuff()
else:
do_something_else()
def main_function():
# do some stuff
if some_condition:
return _sub_function()
else:
return _sub_function(do_the_thing=False)
then I have this test, test_my_module.py:
import unittest
from unittest import mock
import my_module
class TestMyModule(unittest.TestCase):
#mock.patch.object("my_module._sub_function", "__defaults__", (False,))
def test_main_function(self):
print(my_module.main_function())
if __name__ == "__main__":
unittest.main()
I have a function _sub_function that takes a default argument that decides if it performs some steps or not. Normally, main_function calculates when those actions need to be performed and overrides that default argument. Unfortunately, when running tests I can't perform those actions when I normally need to.
So my idea was to use a default argument on _sub_function and in my test to patch the function to monkey-patch that argument to be False so that it skips these actions during testing. Unfortunately I can't use the code in this question because I'm testing main_function, and not _sub_function, so I don't have _sub_function in my test. mock.patch.object can only take the object being patched as an argument, not a string containing the import path of the object (like mock.patch does), so the above code doesn't work, it raises an AttributeError: my_module._sub_function does not have the attribute '__defaults__' on the mock.patch.object() line.
Is there a way to patch a functions default arguments using the string import path of that function.
Or is there a better way to achieve what I want?
The only problem is you are trying to patch an attribute of a str object, not your function:
class TestMyModule(unittest.TestCase):
#mock.patch.object(my_module._sub_function, "__defaults__", (False,))
def test_main_function(self):
print(my_module.main_function())
The AttributeError being raised doesn't make that clear, unfortunately.

How do I patch a method registered by a decorator in Python's datashape?

I'm using the datashape Python package and registering a new type with the #datashape.discover.register decorator. I'd like to test that when I call datashape.discover on an object of the type I'm registering, it calls the function being decorated. I'd also like to do this with good unit testing principles, meaning not actually executing the function being decorated, as it would have side effects I don't want in the test. However, this isn't working.
Here's some sample code to demonstrate the problem:
myfile.py:
#datashape.discover.register(SomeType)
def discover_some_type(data)
...some stuff i don't want done in a unit test...
test_myfile.py:
class TestDiscoverSomeType(unittest.TestCase):
#patch('myfile.discover_some_type')
def test_discover_some_type(self, mock_discover_some_type):
file_to_discover = SomeType()
datashape.discover(file_to_discover)
mock_discover_some_type.assert_called_with(file_to_discover)
The issue seems to be that the function I want mocked is mocked in the body of the test, however, it was not mocked when it was decorated (i.e. when it was imported). The discover.register function essentially internally registers the function being decorated to look it up when discover() is called with an argument of the given type. Unfortunately, it seems to internally register the real function every time, and not the patched version I want, so it will always call the real function.
Any thoughts on how to be able to patch the function being decorated and assert that it is called when datashape.discover is called?
Here's a solution I've found that's only a little hacky:
sometype.py:
def discover_some_type(data):
...some stuff i don't want done in a unit test...
discovery_channel.py:
import sometype
#datashape.discover.register(SomeType)
def discover_some_type(data):
return sometype.discover_some_type(data)
test_sometype.py:
class TestDiscoverSomeType(unittest.TestCase):
#patch('sometype.discover_some_type')
def test_discover_some_type(self, mock_discover_some_type):
import discovery_channel
file_to_discover = SomeType()
datashape.discover(file_to_discover)
mock_discover_some_type.assert_called_with(file_to_discover)
The key is that you have to patch out whatever will actually do stuff before you import the module that has the decorated function that will register the patched function to datashape. This unfortunately means that you can't have your decorated function and the function doing the discovery in the same module (so things that should logically go together are now apart). And you have the somewhat hacky import-in-a-function in your unit test (to trigger the discover.register). But at least it works.

#Patch decorator is not compatible with pytest fixture

I have encountered something mysterious, when using patch decorator from mock package integrated with pytest fixture.
I have two modules:
-----test folder
-------func.py
-------test_test.py
in func.py:
def a():
return 1
def b():
return a()
in test_test.py:
import pytest
from func import a,b
from mock import patch,Mock
#pytest.fixture(scope="module")
def brands():
return 1
mock_b=Mock()
#patch('test_test.b',mock_b)
def test_compute_scores(brands):
a()
It seems that patch decorate is not compatible with pytest fixture. Does anyone have a insight on that? Thanks
When using pytest fixture with mock.patch, test parameter order is crucial.
If you place a fixture parameter before a mocked one:
from unittest import mock
#mock.patch('my.module.my.class')
def test_my_code(my_fixture, mocked_class):
then the mock object will be in my_fixture and mocked_class will be search as a fixture:
fixture 'mocked_class' not found
But, if you reverse the order, placing the fixture parameter at the end:
from unittest import mock
#mock.patch('my.module.my.class')
def test_my_code(mocked_class, my_fixture):
then all will be fine.
As of Python3.3, the mock module has been pulled into the unittest library. There is also a backport (for previous versions of Python) available as the standalone library mock.
Combining these 2 libraries within the same test-suite yields the above-mentioned error:
E fixture 'fixture_name' not found
Within your test-suite's virtual environment, run pip uninstall mock, and make sure you aren't using the backported library alongside the core unittest library. When you re-run your tests after uninstalling, you would see ImportErrors if this were the case.
Replace all instances of this import with from unittest.mock import <stuff>.
Hopefully this answer on an old question will help someone.
First off, the question doesn't include the error, so we don't really know what's up. But I'll try to provide something that helped me.
If you want a test decorated with a patched object, then in order for it to work with pytest you could just do this:
#mock.patch('mocked.module')
def test_me(*args):
mocked_module = args[0]
Or for multiple patches:
#mock.patch('mocked.module1')
#mock.patch('mocked.module')
def test_me(*args):
mocked_module1, mocked_module2 = args
pytest is looking for the names of the fixtures to look up in the test function/method. Providing the *args argument gives us a good workaround the lookup phase. So, to include a fixture with patches, you could do this:
# from question
#pytest.fixture(scope="module")
def brands():
return 1
#mock.patch('mocked.module1')
def test_me(brands, *args):
mocked_module1 = args[0]
This worked for me running python 3.6 and pytest 3.0.6.
If you have multiple patches to be applied, order they are injected is important:
# from question
#pytest.fixture(scope="module")
def brands():
return 1
# notice the order
#patch('my.module.my.class1')
#patch('my.module.my.class2')
def test_list_instance_elb_tg(mocked_class2, mocked_class1, brands):
pass
This doesn't address your question directly, but there is the pytest-mock plugin which allows you to write this instead:
def test_compute_scores(brands, mock):
mock_b = mock.patch('test_test.b')
a()
a) For me the solution was to use a with block inside the test function instead of using a #patch decoration before the test function:
class TestFoo:
def test_baa(self, my_fixture):
with patch(
'module.Class.function_to_patch',
MagicMock(return_value='mocked_result')
) as mocked_function_to_patch:
result= my_fixture.baa('mocked_input')
assert result == 'mocked_result'
mocked_function_to_patch.assert_has_calls([
call('mocked_input')
])
This solution does work inside classes (that are used to structure/group my test methods). Using the with block, you don't need to worry about the order of the arguments. I find it more explicit then the injection mechanism but the code becomes ugly if you patch more then one variable. If you need to patch many dependencies, that might be a signal that your tested function does too many things and that you should refactor it, e.g. by extracting some of the functionality to extra functions.
b) If you are outside classes and do want a patched object to be injected as extra argument in a test method... please note that #patch does not support to define the mock as second argument of the decoration:
#patch('path.to.foo', MagicMock(return_value='foo_value'))
def test_baa(self, my_fixture, mocked_foo):
does not work.
=> Make sure to pass the path as only argument to the decoration. Then define the return value inside the test function:
#patch('path.to.foo')
def test_baa(self, my_fixture, mocked_foo):
mocked_foo.return_value = 'foo_value'
(Unfortunately, this does not seem to work inside classes.)
First let inject the fixture(s), then let inject the variables of the #patch decorations (e.g. 'mocked_foo').
The name of the injected fixture 'my_fixture' needs to be correct. It needs to match the name of the decorated fixture function (or the explicit name used in the fixture decoration).
The name of the injected patch variable 'mocked_foo' does not follow a distinct naming pattern. You can choose it as you like, independent from the corresponding path of the #patch decoration.
If you inject several patched variables, note that the order is reversed: the mocked instance belonging to the last #patch decoration is injected first:
#patch('path.to.foo')
#patch('path.to.qux')
def test_baa(self, my_fixture, mocked_qux, mocked_foo):
mocked_foo.return_value = 'foo_value'
I had the same problem and solution for me was to use mock library in 1.0.1 version (before I was using unittest.mock in 2.6.0 version). Now it works like a charm :)

Categories