Pytest: Setting up mocks with functions without side effects - python

I have a question related the answer of this question:
pytest: setup a mock for every test function
I like the idea of using functions that receive the mock objects via arguments. In this way, the setting up of mocks can be reused. I conclude from the answer that mock objects are mutable in Python and changing them inside the function will have the side effect that they are changed outside. However, I consider it as dangerous to have side effects. So, I suggest the followoing:
def test(self, mock1):
mock1 = setup_mock1_to_always_xxx(mock1)
...
with
def setup_mock1_to_always_xxx(mock1):
# create a copy to avoid side effects
mock1 = mock1.copy() # how to copy a Mock?
mock1.return_value = "my return value"
return mock1
Would this be possible?

I would suggest injecting mocks using pytest fixtures instead of manual mock copying. The function-scoped fixtures (the default ones) reevaluate for each test. Example: assume you have a module
# mod.py
def spam():
return eggs()
def eggs():
return "eggs"
and a test
from unittest.mock import patch
from mod import spam
#patch("mod.eggs")
def test_bacon(mock1):
mock1.return_value = "bacon"
assert spam() == "bacon"
and now you want to refactor it into testing against bacon and bacon with eggs. Move out the patching inside a fixture:
import pytest
from unittest.mock import patch
from mod import spam
#pytest.fixture
def eggs_mock():
with patch("mod.eggs") as mock1:
yield mock1
def test_bacon(eggs_mock):
eggs_mock.return_value = "bacon"
assert spam() == "bacon"
def test_bacon_with_eggs(eggs_mock):
eggs_mock.return_value = "bacon with eggs"
assert spam() == "bacon with eggs"
You now have two different mocks of the mod.eggs function, one unique mock in each test.
unittest-style tests
This approach also works with unittest test classes, although the injection is a bit more verbose since unittest.TestCases don't accept arguments in test methods. This is the same approach as described in this answer of mine. In the example below, I store the eggs_mock fixture return value in a Tests instance attribute via using an additional autouse fixture:
from unittest import TestCase
from unittest.mock import patch
import pytest
from mod import spam
#pytest.fixture
def eggs_mock():
with patch("mod.eggs") as mock1:
yield mock1
class Tests(TestCase):
#pytest.fixture(autouse=True)
def inject_eggs_mock(self, eggs_mock):
self._eggs_mock = eggs_mock
def test_bacon(self):
self._eggs_mock.return_value = "bacon"
assert spam() == "bacon"
def test_bacon_with_eggs(self):
self._eggs_mock.return_value = "bacon with eggs"
assert spam() == "bacon with eggs"

Related

Python: setting default side_effect with create_autospec on class instance

I am trying to use side_effect with unittest.mock.create_autospec on class to set default instance call behavior to raise NotImplementedError.
The problem I am facing is:
I do not want exception to be raise on class __init__.
I do not want to set explicitly all my methods side effect.
I want to use a pytest.fixture in order to make my mock reusable through various tests.
Here a code sample of what I am trying to achieve.
# module.py
class MyClass:
def __init__(self, value):
self.value = value
def compute(self):
return self.value
def foo():
instance = MyClass(42)
return instance.compute()
# test_module.py
from unittest.mock import create_autospec
import module
import pytest
#pytest.fixture(autouse=True)
def my_class(monkeypatch):
# Help me HERE to set side_effect, bellow tests will not work with this settings.
spec_cls = create_autospec(module.MyClass, side_effect=NotImplementedError)
monkeypatch.setattr(module, "MyClass", spec_cls)
return spec_cls("<whatever>")
def test_foo():
with pytest.raises(NotImplementedError):
module.foo()
def test_bar(my_class):
my_class.compute.return_value = 24
assert module.foo() == 24
Not need to use autospec :
import unittest.mock as mocking
import pytest
import so71018132_module as module
#pytest.fixture(autouse=True)
def fake_my_class():
with mocking.patch.object(module.MyClass, "compute") as compute_mock:
compute_mock.side_effect = NotImplementedError # default behaviour
yield compute_mock
def test_foo():
with pytest.raises(NotImplementedError):
module.foo()
def test_bar(fake_my_class):
fake_my_class.side_effect = [24]
# or alternatively, clear the side_effect then set a return_value :
# fake_my_class.side_effect = None
# fake_my_class.return_value = 24
assert module.foo() == 24
passes the 2 tests.
I completely changed the fixture to use unittest.mock.object.patch on the compute method of MyClass so just that is mocked, the rest of the class is used ordinarily.
Also, I had to slightly adjust the test_bar code to correctly alter the mock behavior.

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.

Passing arguments to fixture in TestCase with pytest

How to pass custom arguments to fixture inside test method of unittest.TestCase derived class using pytest?
After searching for definitely too long time I managed to invent solution with usage of doc and threads about wrapping fixtures. I hope somebody will find it useful.
conftest.py
import pytest
#pytest.fixture()
def add(request):
def wrapped(a=10, b=5):
return a + b
request.cls.add = wrapped
add_test.py
import pytest
from unittest import TestCase
#pytest.mark.usefixtures('add')
class AddTestCase(TestCase):
def test_add(self):
# parameters can be passed inside test method
result = self.add(2, 2)
assert result == 4

How to share object from fixture to all tests using pytest?

What is the best way to define an object in a fixture with session scope and autouse=True, so it will be available to all tests?
#pytest.fixture(scope='session', autouse=True)
def setup_func(request):
obj = SomeObj()
Next thing, I want some magic that previously created obj will appear in each test context without the need of each test to define the setup_func fixture.
def test_one():
obj.do_something_fancy()
My recommendation would to add the fixture to conftest.py and make sure to return the object you want to produce from the fixture.
As noted, this makes "autouse" kind of useless.
In the root directory for your tests, add the fixture to a file named conftest.py:
#pytest.fixture(scope='session', autouse=True)
def someobj(request):
return SomeObj()
Any test file beneath the root file will have access to this fixture (for example test_foo.py):
def test_foo(someobj):
assert isinstance(someobj, SomeObj)
Another approach, would be to use a global variable defined in the same test or imported from a module.
For example in conftest.py:
someobj = None
#pytest.fixture(scope='session', autouse=True)
def prep_someobj(request):
someobj = SomeObj()
Then in your test:
from . import conftest
def test_foo():
assert isinstance(conftest.someobj, SomeObj)
In my opinion this is less readable and more cumbersome than the first method.
A more general pattern for this is to return locals() at the end of your conftest and you'll be able to easily reference anything created in the fixture.
conftest.py
#pytest.fixture(scope='session')
def setup_func(request):
obj1 = SomeObj()
obj2 = SomeObj()
return locals()
test_stuff.py
def test_one(setup_func):
setup_func['obj1'].do_something_fancy()
def test_two(setup_func):
setup_func['obj2'].do_something_fancy()
Another possibility is to wrap your tests in a class and use class variables to only define the object instance once. This assumes you are able to wrap all tests in a single class and so this answer may address a less general, but similar use case. For example,
class SomeObj():
"""This object definition may exist in another module and be imported."""
def __init__(self):
self.x = 5
def do_something_fancy(self, y):
return self.x * y
class TestX():
# Object instance to share across tests
someobj = SomeObj()
def test_x(self):
assert TestX.someobj.x == 5
def test_fancy(self):
fancy_factor = 10
result = TestX.someobj.do_something_fancy(fancy_factor)
assert result == 50

Better way to mock class attribute in python unit test

I have a base class that defines a class attribute and some child classes that depend on it, e.g.
class Base(object):
assignment = dict(a=1, b=2, c=3)
I want to unittest this class with different assignments, e.g. empty dictionary, single item, etc. This is extremely simplified of course, it's not a matter of refactoring my classes or tests
The (pytest) tests I have come up with, eventually, that work are
from .base import Base
def test_empty(self):
with mock.patch("base.Base.assignment") as a:
a.__get__ = mock.Mock(return_value={})
assert len(Base().assignment.values()) == 0
def test_single(self):
with mock.patch("base.Base.assignment") as a:
a.__get__ = mock.Mock(return_value={'a':1})
assert len(Base().assignment.values()) == 1
This feels rather complicated and hacky - I don't even fully understand why it works (I am familiar with descriptors though). Does mock automagically transform class attributes into descriptors?
A solution that would feel more logical does not work:
def test_single(self):
with mock.patch("base.Base") as a:
a.assignment = mock.PropertyMock(return_value={'a':1})
assert len(Base().assignment.values()) == 1
or just
def test_single(self):
with mock.patch("base.Base") as a:
a.assignment = {'a':1}
assert len(Base().assignment.values()) == 1
Other variants that I've tried don't work either (assignments remains unchanged in the test).
What's the proper way to mock a class attribute? Is there a better / more understandable way than the one above?
base.Base.assignment is simply replaced with a Mock object. You made it a descriptor by adding a __get__ method.
It's a little verbose and a little unnecessary; you could simply set base.Base.assignment directly:
def test_empty(self):
Base.assignment = {}
assert len(Base().assignment.values()) == 0
This isn't too safe when using test concurrency, of course.
To use a PropertyMock, I'd use:
with patch('base.Base.assignment', new_callable=PropertyMock) as a:
a.return_value = {'a': 1}
or even:
with patch('base.Base.assignment', new_callable=PropertyMock,
return_value={'a': 1}):
Perhaps I'm missing something, but isn't this possible without using PropertyMock?
with mock.patch.object(Base, 'assignment', {'bucket': 'head'}):
# do stuff
To improve readability you can use the #patch decorator:
from mock import patch
from unittest import TestCase
from base import Base
class MyTest(TestCase):
#patch('base.Base.assignment')
def test_empty(self, mock_assignment):
# The `mock_assignment` is a MagicMock instance,
# you can do whatever you want to it.
mock_assignment.__get__.return_value = {}
self.assertEqual(len(Base().assignment.values()), 0)
# ... and so on
You can find more details at http://www.voidspace.org.uk/python/mock/patch.html#mock.patch.
If your class (Queue for example) in already imported inside your test - and you want to patch MAX_RETRY attr - you can use #patch.object or simply better #patch.multiple
from mock import patch, PropertyMock, Mock
from somewhere import Queue
#patch.multiple(Queue, MAX_RETRY=1, some_class_method=Mock)
def test_something(self):
do_something()
#patch.object(Queue, 'MAX_RETRY', return_value=1, new_callable=PropertyMock)
def test_something(self, _mocked):
do_something()
Here is an example how to unit-test your Base class:
mocking multiple class attributes of different types (ie: dict and int)
using the #patch decorator and pytest framework with with python 2.7+ or 3+.
# -*- coding: utf-8 -*-
try: #python 3
from unittest.mock import patch, PropertyMock
except ImportError as e: #python 2
from mock import patch, PropertyMock
from base import Base
#patch('base.Base.assign_dict', new_callable=PropertyMock, return_value=dict(a=1, b=2, c=3))
#patch('base.Base.assign_int', new_callable=PropertyMock, return_value=9765)
def test_type(mock_dict, mock_int):
"""Test if mocked class attributes have correct types"""
assert isinstance(Base().assign_dict, dict)
assert isinstance(Base().assign_int , int)

Categories