Mock objects are useful in unit testing as stand ins for other objects or functions. You might use a mock object instead of the real thing when: the real thing is expensive to create, the real thing requires online resources that might be offline, or you just want to do really fine grained testing. With mock objects you can easily control what they do and then test whether they were used as intended.
There are a number of Python mock libraries but the one discussed here is mock
: http://www.voidspace.org.uk/python/mock/.
import mock
Here I use a mock object to stand in for a function. No matter how it is called it will return 42
.
mock_func = mock.Mock()
mock_func.return_value = 42
print mock_func(6, 9)
42
Mock objects remember how they have been called and you can test that they were called correctly.
mock_func.assert_called_with(6, 9)
If the calling sequences don't match you get an assertion error.
mock_func.assert_called_with(6, 7)
--------------------------------------------------------------------------- AssertionError Traceback (most recent call last) <ipython-input-4-c5e431097d00> in <module>() ----> 1 mock_func.assert_called_with(6, 7) /Users/mrdavis/py-lib/mock-1.0b1-py2.7.egg/mock.pyc in assert_called_with(_mock_self, *args, **kwargs) 822 if self.call_args != (args, kwargs): 823 msg = self._format_mock_failure_message(args, kwargs) --> 824 raise AssertionError(msg) 825 826 AssertionError: Expected call: mock(6, 7) Actual call: mock(6, 9)
assert_called_with
applies only to the most recent call.
Here I use a mock object to stand in for a class and method. It looks very similar to above. A NonCallableMock
is basically the same as a plain Mock
except it cannot be called.
mock_class = mock.NonCallableMock()
mock_class.some_method.return_value = 42
print mock_class.some_method(6, 9)
42
mock_class.some_method.assert_called_once_with(6, 9)
Mock objects can have side effects when called instead of simple return values. One desireable side effect might be to raise an exception to make sure your code under test responds correctly.
mock_func_w_side_effect = mock.Mock()
mock_func_w_side_effect.side_effect = ValueError('Wrong!')
mock_func_w_side_effect()
--------------------------------------------------------------------------- ValueError Traceback (most recent call last) <ipython-input-7-c8ad7335dff3> in <module>() 1 mock_func_w_side_effect = mock.Mock() 2 mock_func_w_side_effect.side_effect = ValueError('Wrong!') ----> 3 mock_func_w_side_effect() /Users/mrdavis/py-lib/mock-1.0b1-py2.7.egg/mock.pyc in __call__(_mock_self, *args, **kwargs) 942 # in the signature 943 _mock_self._mock_check_sig(*args, **kwargs) --> 944 return _mock_self._mock_call(*args, **kwargs) 945 946 /Users/mrdavis/py-lib/mock-1.0b1-py2.7.egg/mock.pyc in _mock_call(_mock_self, *args, **kwargs) 997 if effect is not None: 998 if _is_exception(effect): --> 999 raise effect 1000 1001 if not _callable(effect): ValueError: Wrong!
Another side effect is a function that actually does something, but I couldn't think of many uses for this.
mock_func_w_side_effect.side_effect = lambda x, y: x + y
mock_func_w_side_effect('spam', 'SPAM')
'spamSPAM'
Creating mock objects directly as in the above examples can be useful for constructing objects passed to code under test but you may also want to replace functions and objects used by the code under test. Since you don't have direct access to these you can use mock's patch
utility, which comes in several flavors.
As an example I'll create a toy function to test. It simply calls json.dumps
. (Read more about the json module here: http://docs.python.org/library/json.html.)
import json
def func_with_json(d):
return json.dumps(d)
d = {'a': 1, 'b': [2, 3]} # a simple input for func_with_json
mock.patch
can be used as a context manager. Here it replaces the function json.dumps
. At the end of the code block within the context manager json.dumps
goes back to its normal state.
with mock.patch('json.dumps') as mock_dumps:
mock_dumps.return_value = 'JSON'
r = func_with_json(d)
assert r == 'JSON'
mock_dumps.assert_called_once_with(d)
Outside the context block json.dumps
works as normal:
print json.dumps(d)
{"a": 1, "b": [2, 3]}
mock.patch
can also be used as a function or class decorator, replacing an object inside the function or class.
Here we use mock.patch
to replace json.dumps
within a test function. The mock object replacing json.dumps
is passed to the test function as an argument.
@mock.patch('json.dumps')
def test_func_with_json(mock_dumps):
mock_dumps.return_value = 'JSON'
r = func_with_json({'c': {'d': [4]}})
assert r == 'JSON'
mock_dumps.assert_called_once_with(d) # whoops, we didn't pass in d, this should fail.
test_func_with_json()
--------------------------------------------------------------------------- AssertionError Traceback (most recent call last) <ipython-input-13-0c4563c6a92d> in <module>() 5 assert r == 'JSON' 6 mock_dumps.assert_called_once_with(d) # whoops, we didn't pass in d, this should fail. ----> 7 test_func_with_json() /Users/mrdavis/py-lib/mock-1.0b1-py2.7.egg/mock.pyc in patched(*args, **keywargs) 1188 1189 args += tuple(extra_args) -> 1190 return func(*args, **keywargs) 1191 except: 1192 if (patching not in entered_patchers and <ipython-input-13-0c4563c6a92d> in test_func_with_json(mock_dumps) 4 r = func_with_json({'c': {'d': [4]}}) 5 assert r == 'JSON' ----> 6 mock_dumps.assert_called_once_with(d) # whoops, we didn't pass in d, this should fail. 7 test_func_with_json() /Users/mrdavis/py-lib/mock-1.0b1-py2.7.egg/mock.pyc in assert_called_once_with(_mock_self, *args, **kwargs) 833 self.call_count) 834 raise AssertionError(msg) --> 835 return self.assert_called_with(*args, **kwargs) 836 837 /Users/mrdavis/py-lib/mock-1.0b1-py2.7.egg/mock.pyc in assert_called_with(_mock_self, *args, **kwargs) 822 if self.call_args != (args, kwargs): 823 msg = self._format_mock_failure_message(args, kwargs) --> 824 raise AssertionError(msg) 825 826 AssertionError: Expected call: dumps({'a': 1, 'b': [2, 3]}) Actual call: dumps({'c': {'d': [4]}})
There are a number of different kinds of patches and different ways to use them. For more information refer to the mock documentation.