We have been using Mock for python for a while.
Now, we have a situation in which we want to mock a function
def foo(self, my_param):
#do something here, assign something to my_result
return my_result
Normally, the way to mock this would be (assuming foo being part of an object)
self.foo = MagicMock(return_value="mocked!")
Even, if i call foo() a couple of times i can use
self.foo = MagicMock(side_effect=["mocked once", "mocked twice!"])
Now, I am facing a situation in which I want to return a fixed value when the input parameter has a particular value. So if let's say "my_param" is equal to "something" then I want to return "my_cool_mock"
This seems to be available on mockito for python
when(dummy).foo("something").thenReturn("my_cool_mock")
I have been searching on how to achieve the same with Mock with no success?
Any ideas?
9 Answers 9
If
side_effect_funcis a function then whatever that function returns is what calls to the mock return. Theside_effect_funcfunction is called with the same arguments as the mock. This allows you to vary the return value of the call dynamically, based on the input:>>> def side_effect_func(value): ... return value + 1 ... >>> m = MagicMock(side_effect=side_effect_func) >>> m(1) 2 >>> m(2) 3 >>> m.mock_calls [call(1), call(2)]
4 Comments
side effect is the accurate term: en.wikipedia.org/wiki/Side_effect_(computer_science) CallableMixin.side_effect, but that the separate function defined in the example has the same name.As indicated at Python Mock object with method called multiple times
A solution is to write my own side_effect
def my_side_effect(*args, **kwargs):
if args[0] == 42:
return "Called with 42"
elif args[0] == 43:
return "Called with 43"
elif kwargs['foo'] == 7:
return "Foo is seven"
mockobj.mockmethod.side_effect = my_side_effect
That does the trick
1 Comment
Side effect takes a function (which can also be a lambda function), so for simple cases you may use:
m = MagicMock(side_effect=(lambda x: x+1))
Comments
If you "want to return a fixed value when the input parameter has a particular value", maybe you don't even need a mock and could use a dict along with its get method:
foo = {'input1': 'value1', 'input2': 'value2'}.get
foo('input1') # value1
foo('input2') # value2
This works well when your fake's output is a mapping of input. When it's a function of input I'd suggest using side_effect as per Amber's answer.
You can also use a combination of both if you want to preserve Mock's capabilities (assert_called_once, call_count etc):
self.mock.side_effect = {'input1': 'value1', 'input2': 'value2'}.get
2 Comments
I've ended up here looking for "how to mock a function based on input arguments" and I finally solved this creating a simple aux function:
def mock_responses(responses, default_response=None):
return lambda input: responses[input] if input in responses else default_response
Now:
my_mock.foo.side_effect = mock_responses(
{
'x': 42,
'y': [1,2,3]
})
my_mock.goo.side_effect = mock_responses(
{
'hello': 'world'
},
default_response='hi')
...
my_mock.foo('x') # => 42
my_mock.foo('y') # => [1,2,3]
my_mock.foo('unknown') # => None
my_mock.goo('hello') # => 'world'
my_mock.goo('ey') # => 'hi'
Hope this will help someone!
Comments
Although side_effect can achieve the goal, it is not so convenient to setup side_effect function for each test case.
I write a lightweight Mock (which is called NextMock) to enhance the built-in mock to address this problem, here is a simple example:
from nextmock import Mock
m = Mock()
m.with_args(1, 2, 3).returns(123)
assert m(1, 2, 3) == 123
assert m(3, 2, 1) != 123
It also supports argument matcher:
from nextmock import Arg, Mock
m = Mock()
m.with_args(1, 2, Arg.Any).returns(123)
assert m(1, 2, 1) == 123
assert m(1, 2, "123") == 123
Hope this package could make testing more pleasant. Feel free to give any feedback.
Comments
You can also use partial from functools if you want to use a function that takes parameters but the function you are mocking does not. E.g. like this:
def mock_year(year):
return datetime.datetime(year, 11, 28, tzinfo=timezone.utc)
@patch('django.utils.timezone.now', side_effect=partial(mock_year, year=2020))
This will return a callable that doesn't accept parameters (like Django's timezone.now()), but my mock_year function does.
1 Comment
got multiple values for argument.Just to show another way of doing it:
def mock_isdir(path):
return path in ['/var/log', '/var/log/apache2', '/var/log/tomcat']
with mock.patch('os.path.isdir') as os_path_isdir:
os_path_isdir.side_effect = mock_isdir
Comments
You can also use @mock.patch.object:
Let's say a module my_module.py uses pandas to read from a database and we would like to test this module by mocking pd.read_sql_table method (which takes table_name as argument).
What you can do is to create (inside your test) a db_mock method that returns different objects depending on the argument provided:
def db_mock(**kwargs):
if kwargs['table_name'] == 'table_1':
# return some DataFrame
elif kwargs['table_name'] == 'table_2':
# return some other DataFrame
In your test function you then do:
import my_module as my_module_imported
@mock.patch.object(my_module_imported.pd, "read_sql_table", new_callable=lambda: db_mock)
def test_my_module(mock_read_sql_table):
# You can now test any methods from `my_module`, e.g. `foo` and any call this
# method does to `read_sql_table` will be mocked by `db_mock`, e.g.
ret = my_module_imported.foo(table_name='table_1')
# `ret` is some DataFrame returned by `db_mock`
Comments
Explore related questions
See similar questions with these tags.
monkeypatch. Monkeypatch is more for "replace this function for sake of testing," whereas Mock is what you use when you also want to check themock_callsor make assertions about what it was called with and so on. There is a place for both, and I often use both at different times in a given test file.