Reputation: 1240
The structure of my modules:
foo:
- load() # from DB
bar:
- check() # with user
- take_action()
I want to test take_action (which basically loads values and checks with user before taking action) by mocking both load and check.
Here are the mocks:
mock_load = Mock(side_effects=[<>, <>, <>]) # different data sets
mock_check = Mock(return_value=True) # User approval
How do I use patch.multiple
to achieve this using Python 2.6?
with patch.multiple(??):
# proceed to test
take_action
Upvotes: 23
Views: 61484
Reputation: 5508
As of Python 3.10 you can achieve this with Parenthesized Context Managers like so:
from unittest.mock import patch
def test_something():
with (
patch("foo.load", side_effect=["a","b","c"]), # don't have to have `as`
patch("bar.check", return_value=True) as mock_check, # example using `as`
):
# proceed to test
You can also manually nest. This works also if still using End-Of-Lifed Python 2.6 or 2.7 you can using nesting as shown in python 3 docs or python 2 docs:
with patch("foo.load", side_effect=["a","b","c"]) as mock_load:
with patch("bar.check",return_value=True)) as mock_check:
# proceed to test
# or you can use comma separated like version that has this structure
#
# with A() as a, B() as b:
# ...
#
# which, if we applied it, would easily become a long one liner like so
with patch("bar.check",return_value=True)) as mock_check, patch("foo.load", side_effect=["a","b","c"]) as mock_load:
# proceed to test
In those environments, I usually opt for preserving clarity by doing it like this:
def cron():
with patch(
"foo.load", side_effect=["a","b","c"]
) as mock_load, patch(
"bar.check", return_value=True
) as mock_check, patch(
"spam.Publish", return_value=42
) as mock_publish:
# proceed to test
Upvotes: 12
Reputation: 23711
The short answer is no you cannot use patch.multiple()
to do it. As described in patch.multiple all arguments will be applied to all created mocks and all arguments MUST be attribute of the same object. You MUST do it one of the time by single patch calls.
Unfortunately you are using python 2.6 so you can use just nested
fron contextlib
like pointed in python: create a "with" block on several context managers and Multiple context `with` statement in Python 2.6.
Maybe the more cleaner and simple way to do it is use @patch
as decorator:
@patch("foo.load",side_effects=["a","b","c"])
@patch("bar.check",return_value=True)
def test_mytest(mock_check,mock_load):
take_action()
assert mock_load.called
assert mock_check.called
If you need it in all tests of a test class you can decorate the class and use the mocks in all test methods:
@patch("foo.load",side_effects=["a","b","c"])
@patch("bar.check",return_value=True)
class TestMyTest(unittest.TestCase)
def test_mytestA(self,mock_check,mock_load):
take_action()
self.assertTrue(mock_load.called)
self.assertTrue(mock_check.called)
def test_mytestA(self,mock_check,mock_load):
mock_check.return_value = False
take_action()
self.assertTrue(mock_load.called)
self.assertTrue(mock_check.called)
Finally you can do it by using with
and contextlib
and the first example become:
from contextlib import nested
with nested(patch("foo.load",side_effects=["a","b","c"]), patch("bar.check",return_value=True)) as (mock_load, mock_check):
take_action()
assert mock_load.called
assert mock_check.called
... Or nest it by hand ....
with patch("foo.load",side_effects=["a","b","c"]) as mock_load:
with patch("bar.check",return_value=True)) as mock_check:
take_action()
assert mock_load.called
assert mock_check.called
My feel is that decorators are the most readable and simple to use.
Upvotes: 57