Skip to content

Instantly share code, notes, and snippets.

@jiffyclub
Created September 11, 2012 20:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jiffyclub/3701929 to your computer and use it in GitHub Desktop.
Save jiffyclub/3701929 to your computer and use it in GitHub Desktop.
A short demo of the mock library in an IPython notebook.
Display the source blob
Display the rendered blob
Raw
{
"metadata": {
"name": "mock Demo"
},
"nbformat": 3,
"nbformat_minor": 0,
"worksheets": [
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## A very short introduction to the mock library\n",
"\n",
"[Mock objects](http://en.wikipedia.org/wiki/Mock_object) 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.\n",
"\n",
"There are a number of Python mock libraries but the one discussed here is `mock`: http://www.voidspace.org.uk/python/mock/."
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"import mock"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 1
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Mock a function\n",
"\n",
"Here I use a mock object to stand in for a function. No matter how it is called it will return `42`."
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"mock_func = mock.Mock()\n",
"mock_func.return_value = 42\n",
"print mock_func(6, 9)"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"42\n"
]
}
],
"prompt_number": 2
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Mock objects remember how they have been called and you can test that they were called correctly."
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"mock_func.assert_called_with(6, 9)"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 3
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"If the calling sequences don't match you get an assertion error."
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"mock_func.assert_called_with(6, 7)"
],
"language": "python",
"metadata": {},
"outputs": [
{
"ename": "AssertionError",
"evalue": "Expected call: mock(6, 7)\nActual call: mock(6, 9)",
"output_type": "pyerr",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m\n\u001b[0;31mAssertionError\u001b[0m Traceback (most recent call last)",
"\u001b[0;32m<ipython-input-4-c5e431097d00>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mmock_func\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0massert_called_with\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m6\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m7\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[0;32m/Users/mrdavis/py-lib/mock-1.0b1-py2.7.egg/mock.pyc\u001b[0m in \u001b[0;36massert_called_with\u001b[0;34m(_mock_self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 822\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcall_args\u001b[0m \u001b[0;34m!=\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 823\u001b[0m \u001b[0mmsg\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_format_mock_failure_message\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 824\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mAssertionError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmsg\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 825\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 826\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;31mAssertionError\u001b[0m: Expected call: mock(6, 7)\nActual call: mock(6, 9)"
]
}
],
"prompt_number": 4
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"`assert_called_with` applies only to the most recent call."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Mock a non-callable class\n",
"\n",
"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."
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"mock_class = mock.NonCallableMock()\n",
"mock_class.some_method.return_value = 42\n",
"print mock_class.some_method(6, 9)"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"42\n"
]
}
],
"prompt_number": 5
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"mock_class.some_method.assert_called_once_with(6, 9)"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 6
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Side effects\n",
"\n",
"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."
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"mock_func_w_side_effect = mock.Mock()\n",
"mock_func_w_side_effect.side_effect = ValueError('Wrong!')\n",
"mock_func_w_side_effect()"
],
"language": "python",
"metadata": {},
"outputs": [
{
"ename": "ValueError",
"evalue": "Wrong!",
"output_type": "pyerr",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m\n\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)",
"\u001b[0;32m<ipython-input-7-c8ad7335dff3>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0mmock_func_w_side_effect\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mmock\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mMock\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[0mmock_func_w_side_effect\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mside_effect\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mValueError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'Wrong!'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 3\u001b[0;31m \u001b[0mmock_func_w_side_effect\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[0;32m/Users/mrdavis/py-lib/mock-1.0b1-py2.7.egg/mock.pyc\u001b[0m in \u001b[0;36m__call__\u001b[0;34m(_mock_self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 942\u001b[0m \u001b[0;31m# in the signature\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 943\u001b[0m \u001b[0m_mock_self\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_mock_check_sig\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 944\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0m_mock_self\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_mock_call\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 945\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 946\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;32m/Users/mrdavis/py-lib/mock-1.0b1-py2.7.egg/mock.pyc\u001b[0m in \u001b[0;36m_mock_call\u001b[0;34m(_mock_self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 997\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0meffect\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0mNone\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 998\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0m_is_exception\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0meffect\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 999\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0meffect\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 1000\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1001\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0m_callable\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0meffect\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;31mValueError\u001b[0m: Wrong!"
]
}
],
"prompt_number": 7
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Another side effect is a function that actually does something, but I couldn't think of many uses for this."
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"mock_func_w_side_effect.side_effect = lambda x, y: x + y\n",
"mock_func_w_side_effect('spam', 'SPAM')"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "pyout",
"prompt_number": 8,
"text": [
"'spamSPAM'"
]
}
],
"prompt_number": 8
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Patching\n",
"\n",
"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.\n",
"\n",
"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.)"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"import json\n",
"def func_with_json(d):\n",
" return json.dumps(d)"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 9
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"d = {'a': 1, 'b': [2, 3]} # a simple input for func_with_json"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 10
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"`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."
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"with mock.patch('json.dumps') as mock_dumps:\n",
" mock_dumps.return_value = 'JSON'\n",
" r = func_with_json(d)\n",
" assert r == 'JSON'\n",
" mock_dumps.assert_called_once_with(d)"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 11
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Outside the context block `json.dumps` works as normal:"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"print json.dumps(d)"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"{\"a\": 1, \"b\": [2, 3]}\n"
]
}
],
"prompt_number": 12
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"`mock.patch` can also be used as a function or class decorator, replacing an object inside the function or class.\n",
"\n",
"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."
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"@mock.patch('json.dumps')\n",
"def test_func_with_json(mock_dumps):\n",
" mock_dumps.return_value = 'JSON'\n",
" r = func_with_json({'c': {'d': [4]}})\n",
" assert r == 'JSON'\n",
" mock_dumps.assert_called_once_with(d) # whoops, we didn't pass in d, this should fail.\n",
"test_func_with_json()"
],
"language": "python",
"metadata": {},
"outputs": [
{
"ename": "AssertionError",
"evalue": "Expected call: dumps({'a': 1, 'b': [2, 3]})\nActual call: dumps({'c': {'d': [4]}})",
"output_type": "pyerr",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m\n\u001b[0;31mAssertionError\u001b[0m Traceback (most recent call last)",
"\u001b[0;32m<ipython-input-13-0c4563c6a92d>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m()\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0;32massert\u001b[0m \u001b[0mr\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;34m'JSON'\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 6\u001b[0m \u001b[0mmock_dumps\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0massert_called_once_with\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0md\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;31m# whoops, we didn't pass in d, this should fail.\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 7\u001b[0;31m \u001b[0mtest_func_with_json\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[0;32m/Users/mrdavis/py-lib/mock-1.0b1-py2.7.egg/mock.pyc\u001b[0m in \u001b[0;36mpatched\u001b[0;34m(*args, **keywargs)\u001b[0m\n\u001b[1;32m 1188\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1189\u001b[0m \u001b[0margs\u001b[0m \u001b[0;34m+=\u001b[0m \u001b[0mtuple\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mextra_args\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 1190\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mfunc\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkeywargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 1191\u001b[0m \u001b[0;32mexcept\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1192\u001b[0m if (patching not in entered_patchers and\n",
"\u001b[0;32m<ipython-input-13-0c4563c6a92d>\u001b[0m in \u001b[0;36mtest_func_with_json\u001b[0;34m(mock_dumps)\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[0mr\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mfunc_with_json\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m{\u001b[0m\u001b[0;34m'c'\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m{\u001b[0m\u001b[0;34m'd'\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0;36m4\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m}\u001b[0m\u001b[0;34m}\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0;32massert\u001b[0m \u001b[0mr\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;34m'JSON'\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 6\u001b[0;31m \u001b[0mmock_dumps\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0massert_called_once_with\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0md\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;31m# whoops, we didn't pass in d, this should fail.\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 7\u001b[0m \u001b[0mtest_func_with_json\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;32m/Users/mrdavis/py-lib/mock-1.0b1-py2.7.egg/mock.pyc\u001b[0m in \u001b[0;36massert_called_once_with\u001b[0;34m(_mock_self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 833\u001b[0m self.call_count)\n\u001b[1;32m 834\u001b[0m \u001b[0;32mraise\u001b[0m \u001b[0mAssertionError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmsg\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 835\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0massert_called_with\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 836\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 837\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;32m/Users/mrdavis/py-lib/mock-1.0b1-py2.7.egg/mock.pyc\u001b[0m in \u001b[0;36massert_called_with\u001b[0;34m(_mock_self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 822\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcall_args\u001b[0m \u001b[0;34m!=\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 823\u001b[0m \u001b[0mmsg\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_format_mock_failure_message\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 824\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mAssertionError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmsg\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 825\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 826\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;31mAssertionError\u001b[0m: Expected call: dumps({'a': 1, 'b': [2, 3]})\nActual call: dumps({'c': {'d': [4]}})"
]
}
],
"prompt_number": 13
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"There are a number of different kinds of patches and different ways to use them. For more information refer to the [mock documentation](http://www.voidspace.org.uk/python/mock/)."
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [],
"language": "python",
"metadata": {},
"outputs": []
}
],
"metadata": {}
}
]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment