Conditional mocking: Call original function if condition does match
Question:
How can I conditionally call the original method in a mock?
In this example I only want to fake a return value if bar=='x'
. Otherwise I want to call the original method.
def mocked_some_method(bar):
if bar=='x':
return 'fake'
return some_how_call_original_method(bar)
with mock.patch('mylib.foo.some_method', mocked_some_method):
do_some_stuff()
I know that it is a bit strange. If I want to fake mylib.foo.some_method
in side do_some_stuff()
it should be condition-less. All (not some) calls to some_method
should be mocked.
In my case it is an integration test, not a s tiny unittest and mylib.foo.some_method
is a kind of dispatcher which gets used very often. And in one case I need to fake the result.
Update
I wrote this question four years ago. Today, it feels very strange to do conditional mocking. Mocks should only get used in tests. Tests (and production code) should be simple and small. Tests should be conditionless. As I wrote this question, we still used huge production methods and long test. Today, I follow these rules (simple methods, conditionless tests …). I wrote my findings down: my programming guidelines
Answers:
If you need just replace behavior without care of mock’s calls assert function you can use new
argument; otherwise you can use side_effect
that take a callable.
I guess that some_method
is a object method (instead of a staticmethod
) so you need a reference its object to call it. Your wrapper should declare as first argument the object and your patch use autospec=True
to use the correct signature for side_effect
case.
Final trick is save the original method reference and use it to make the call.
orig = mylib.foo.some_method
def mocked_some_method(self, bar):
if bar=='x':
return 'fake'
return orig(self, bar)
#Just replace:
with mock.patch('mylib.foo.some_method', new=mocked_some_method):
do_some_stuff()
#Replace by mock
with mock.patch('mylib.foo.some_method', side_effect=mocked_some_method, autospec=True) as mock_some_method:
do_some_stuff()
assert mock_some_method.called
Here is an example of how you can dynamically patch class method and execute the original method when you need
- Code which needs to be tested
class CheckMockMethod:
def get_value_x_10(self, value):
"""Method which is going to be patched"""
return value*10
def wrapper_func():
"""Function which is called from test"""
for i in [1, 2, 3]:
print(CheckMockMethod().get_value_x_10(i))
- ContextManager test helper for mock.patcher
import mock
from contextlib import contextmanager
@contextmanager
def mock_patch_method_original(mock_path, original_method, results):
results = iter(results)
def side_effect(self, *args, **kwargs):
value = next(results)
if value == '__original__':
side_effect.self = self
return original_method(self, *args, **kwargs)
else:
return value
patcher = mock.patch(mock_path, autospec=True, side_effect=side_effect)
yield patcher.start()
patcher.stop()
- Example test method, see the value of results var,
__original__
when you want to return original value
from somewhere import CheckMockMethod
from test_helpers import mock_patch_method_original
def test_wrapper(self):
path = '<import-path>.CheckMockMethod.get_value_x_10'
orig = CheckMockMethod.get_value_x_10
results = [1000, '__original__', 3000]
with mock_patch_method_original(path, original_method=orig, results=results):
wrapper_func()
-
Results
With mock you will see
1000, 20, 3000
where 1000 and 3000 are patched values and 20 is original
Returning unittest.mock.DEFAULT
using return_value or side_effects will call original method. This should allow to avoid some of the wrappers.
How can I conditionally call the original method in a mock?
In this example I only want to fake a return value if bar=='x'
. Otherwise I want to call the original method.
def mocked_some_method(bar):
if bar=='x':
return 'fake'
return some_how_call_original_method(bar)
with mock.patch('mylib.foo.some_method', mocked_some_method):
do_some_stuff()
I know that it is a bit strange. If I want to fake mylib.foo.some_method
in side do_some_stuff()
it should be condition-less. All (not some) calls to some_method
should be mocked.
In my case it is an integration test, not a s tiny unittest and mylib.foo.some_method
is a kind of dispatcher which gets used very often. And in one case I need to fake the result.
Update
I wrote this question four years ago. Today, it feels very strange to do conditional mocking. Mocks should only get used in tests. Tests (and production code) should be simple and small. Tests should be conditionless. As I wrote this question, we still used huge production methods and long test. Today, I follow these rules (simple methods, conditionless tests …). I wrote my findings down: my programming guidelines
If you need just replace behavior without care of mock’s calls assert function you can use new
argument; otherwise you can use side_effect
that take a callable.
I guess that some_method
is a object method (instead of a staticmethod
) so you need a reference its object to call it. Your wrapper should declare as first argument the object and your patch use autospec=True
to use the correct signature for side_effect
case.
Final trick is save the original method reference and use it to make the call.
orig = mylib.foo.some_method
def mocked_some_method(self, bar):
if bar=='x':
return 'fake'
return orig(self, bar)
#Just replace:
with mock.patch('mylib.foo.some_method', new=mocked_some_method):
do_some_stuff()
#Replace by mock
with mock.patch('mylib.foo.some_method', side_effect=mocked_some_method, autospec=True) as mock_some_method:
do_some_stuff()
assert mock_some_method.called
Here is an example of how you can dynamically patch class method and execute the original method when you need
- Code which needs to be tested
class CheckMockMethod:
def get_value_x_10(self, value):
"""Method which is going to be patched"""
return value*10
def wrapper_func():
"""Function which is called from test"""
for i in [1, 2, 3]:
print(CheckMockMethod().get_value_x_10(i))
- ContextManager test helper for mock.patcher
import mock
from contextlib import contextmanager
@contextmanager
def mock_patch_method_original(mock_path, original_method, results):
results = iter(results)
def side_effect(self, *args, **kwargs):
value = next(results)
if value == '__original__':
side_effect.self = self
return original_method(self, *args, **kwargs)
else:
return value
patcher = mock.patch(mock_path, autospec=True, side_effect=side_effect)
yield patcher.start()
patcher.stop()
- Example test method, see the value of results var,
__original__
when you want to return original value
from somewhere import CheckMockMethod
from test_helpers import mock_patch_method_original
def test_wrapper(self):
path = '<import-path>.CheckMockMethod.get_value_x_10'
orig = CheckMockMethod.get_value_x_10
results = [1000, '__original__', 3000]
with mock_patch_method_original(path, original_method=orig, results=results):
wrapper_func()
-
Results
With mock you will see
1000, 20, 3000
where 1000 and 3000 are patched values and 20 is original
Returning unittest.mock.DEFAULT
using return_value or side_effects will call original method. This should allow to avoid some of the wrappers.