Wed, 31 Dec 2014 07:16:47 +0100
Revert simplistic fix pending revisit of Mozilla integration attempt.
michael@0 | 1 | mocksignature |
michael@0 | 2 | ============= |
michael@0 | 3 | |
michael@0 | 4 | .. currentmodule:: mock |
michael@0 | 5 | |
michael@0 | 6 | .. note:: |
michael@0 | 7 | |
michael@0 | 8 | :ref:`auto-speccing`, added in mock 0.8, is a more advanced version of |
michael@0 | 9 | `mocksignature` and can be used for many of the same use cases. |
michael@0 | 10 | |
michael@0 | 11 | A problem with using mock objects to replace real objects in your tests is that |
michael@0 | 12 | :class:`Mock` can be *too* flexible. Your code can treat the mock objects in |
michael@0 | 13 | any way and you have to manually check that they were called correctly. If your |
michael@0 | 14 | code calls functions or methods with the wrong number of arguments then mocks |
michael@0 | 15 | don't complain. |
michael@0 | 16 | |
michael@0 | 17 | The solution to this is `mocksignature`, which creates functions with the |
michael@0 | 18 | same signature as the original, but delegating to a mock. You can interrogate |
michael@0 | 19 | the mock in the usual way to check it has been called with the *right* |
michael@0 | 20 | arguments, but if it is called with the wrong number of arguments it will |
michael@0 | 21 | raise a `TypeError` in the same way your production code would. |
michael@0 | 22 | |
michael@0 | 23 | Another advantage is that your mocked objects are real functions, which can |
michael@0 | 24 | be useful when your code uses |
michael@0 | 25 | `inspect <http://docs.python.org/library/inspect.html>`_ or depends on |
michael@0 | 26 | functions being function objects. |
michael@0 | 27 | |
michael@0 | 28 | .. function:: mocksignature(func, mock=None, skipfirst=False) |
michael@0 | 29 | |
michael@0 | 30 | Create a new function with the same signature as `func` that delegates |
michael@0 | 31 | to `mock`. If `skipfirst` is True the first argument is skipped, useful |
michael@0 | 32 | for methods where `self` needs to be omitted from the new function. |
michael@0 | 33 | |
michael@0 | 34 | If you don't pass in a `mock` then one will be created for you. |
michael@0 | 35 | |
michael@0 | 36 | Functions returned by `mocksignature` have many of the same attributes |
michael@0 | 37 | and assert methods as a mock object. |
michael@0 | 38 | |
michael@0 | 39 | The mock is set as the `mock` attribute of the returned function for easy |
michael@0 | 40 | access. |
michael@0 | 41 | |
michael@0 | 42 | `mocksignature` can also be used with classes. It copies the signature of |
michael@0 | 43 | the `__init__` method. |
michael@0 | 44 | |
michael@0 | 45 | When used with callable objects (instances) it copies the signature of the |
michael@0 | 46 | `__call__` method. |
michael@0 | 47 | |
michael@0 | 48 | `mocksignature` will work out if it is mocking the signature of a method on |
michael@0 | 49 | an instance or a method on a class and do the "right thing" with the `self` |
michael@0 | 50 | argument in both cases. |
michael@0 | 51 | |
michael@0 | 52 | Because of a limitation in the way that arguments are collected by functions |
michael@0 | 53 | created by `mocksignature` they are *always* passed as positional arguments |
michael@0 | 54 | (including defaults) and not keyword arguments. |
michael@0 | 55 | |
michael@0 | 56 | |
michael@0 | 57 | mocksignature api |
michael@0 | 58 | ----------------- |
michael@0 | 59 | |
michael@0 | 60 | Although the objects returned by `mocksignature` api are real function objects, |
michael@0 | 61 | they have much of the same api as the :class:`Mock` class. This includes the |
michael@0 | 62 | assert methods: |
michael@0 | 63 | |
michael@0 | 64 | .. doctest:: |
michael@0 | 65 | |
michael@0 | 66 | >>> def func(a, b, c): |
michael@0 | 67 | ... pass |
michael@0 | 68 | ... |
michael@0 | 69 | >>> func2 = mocksignature(func) |
michael@0 | 70 | >>> func2.called |
michael@0 | 71 | False |
michael@0 | 72 | >>> func2.return_value = 3 |
michael@0 | 73 | >>> func2(1, 2, 3) |
michael@0 | 74 | 3 |
michael@0 | 75 | >>> func2.called |
michael@0 | 76 | True |
michael@0 | 77 | >>> func2.assert_called_once_with(1, 2, 3) |
michael@0 | 78 | >>> func2.assert_called_with(1, 2, 4) |
michael@0 | 79 | Traceback (most recent call last): |
michael@0 | 80 | ... |
michael@0 | 81 | AssertionError: Expected call: mock(1, 2, 4) |
michael@0 | 82 | Actual call: mock(1, 2, 3) |
michael@0 | 83 | >>> func2.call_count |
michael@0 | 84 | 1 |
michael@0 | 85 | >>> func2.side_effect = IndexError |
michael@0 | 86 | >>> func2(4, 5, 6) |
michael@0 | 87 | Traceback (most recent call last): |
michael@0 | 88 | ... |
michael@0 | 89 | IndexError |
michael@0 | 90 | |
michael@0 | 91 | The mock object that is being delegated to is available as the `mock` attribute |
michael@0 | 92 | of the function created by `mocksignature`. |
michael@0 | 93 | |
michael@0 | 94 | .. doctest:: |
michael@0 | 95 | |
michael@0 | 96 | >>> func2.mock.mock_calls |
michael@0 | 97 | [call(1, 2, 3), call(4, 5, 6)] |
michael@0 | 98 | |
michael@0 | 99 | The methods and attributes available on functions returned by `mocksignature` |
michael@0 | 100 | are: |
michael@0 | 101 | |
michael@0 | 102 | :meth:`~Mock.assert_any_call`, :meth:`~Mock.assert_called_once_with`, |
michael@0 | 103 | :meth:`~Mock.assert_called_with`, :meth:`~Mock.assert_has_calls`, |
michael@0 | 104 | :attr:`~Mock.call_args`, :attr:`~Mock.call_args_list`, |
michael@0 | 105 | :attr:`~Mock.call_count`, :attr:`~Mock.called`, |
michael@0 | 106 | :attr:`~Mock.method_calls`, `mock`, :attr:`~Mock.mock_calls`, |
michael@0 | 107 | :meth:`~Mock.reset_mock`, :attr:`~Mock.return_value`, and |
michael@0 | 108 | :attr:`~Mock.side_effect`. |
michael@0 | 109 | |
michael@0 | 110 | |
michael@0 | 111 | Example use |
michael@0 | 112 | ----------- |
michael@0 | 113 | |
michael@0 | 114 | Basic use |
michael@0 | 115 | ~~~~~~~~~ |
michael@0 | 116 | |
michael@0 | 117 | .. doctest:: |
michael@0 | 118 | |
michael@0 | 119 | >>> def function(a, b, c=None): |
michael@0 | 120 | ... pass |
michael@0 | 121 | ... |
michael@0 | 122 | >>> mock = Mock() |
michael@0 | 123 | >>> function = mocksignature(function, mock) |
michael@0 | 124 | >>> function() |
michael@0 | 125 | Traceback (most recent call last): |
michael@0 | 126 | ... |
michael@0 | 127 | TypeError: <lambda>() takes at least 2 arguments (0 given) |
michael@0 | 128 | >>> function.return_value = 'some value' |
michael@0 | 129 | >>> function(1, 2, 'foo') |
michael@0 | 130 | 'some value' |
michael@0 | 131 | >>> function.assert_called_with(1, 2, 'foo') |
michael@0 | 132 | |
michael@0 | 133 | |
michael@0 | 134 | Keyword arguments |
michael@0 | 135 | ~~~~~~~~~~~~~~~~~ |
michael@0 | 136 | |
michael@0 | 137 | Note that arguments to functions created by `mocksignature` are always passed |
michael@0 | 138 | in to the underlying mock by position even when called with keywords: |
michael@0 | 139 | |
michael@0 | 140 | .. doctest:: |
michael@0 | 141 | |
michael@0 | 142 | >>> def function(a, b, c=None): |
michael@0 | 143 | ... pass |
michael@0 | 144 | ... |
michael@0 | 145 | >>> function = mocksignature(function) |
michael@0 | 146 | >>> function.return_value = None |
michael@0 | 147 | >>> function(1, 2) |
michael@0 | 148 | >>> function.assert_called_with(1, 2, None) |
michael@0 | 149 | |
michael@0 | 150 | |
michael@0 | 151 | Mocking methods and self |
michael@0 | 152 | ~~~~~~~~~~~~~~~~~~~~~~~~ |
michael@0 | 153 | |
michael@0 | 154 | When you use `mocksignature` to replace a method on a class then `self` |
michael@0 | 155 | will be included in the method signature - and you will need to include |
michael@0 | 156 | the instance when you do your asserts. |
michael@0 | 157 | |
michael@0 | 158 | As a curious factor of the way Python (2) wraps methods fetched from a class, |
michael@0 | 159 | we can *get* the `return_value` from a function set on a class, but we can't |
michael@0 | 160 | set it. We have to do this through the exposed `mock` attribute instead: |
michael@0 | 161 | |
michael@0 | 162 | .. doctest:: |
michael@0 | 163 | |
michael@0 | 164 | >>> class SomeClass(object): |
michael@0 | 165 | ... def method(self, a, b, c=None): |
michael@0 | 166 | ... pass |
michael@0 | 167 | ... |
michael@0 | 168 | >>> SomeClass.method = mocksignature(SomeClass.method) |
michael@0 | 169 | >>> SomeClass.method.mock.return_value = None |
michael@0 | 170 | >>> instance = SomeClass() |
michael@0 | 171 | >>> instance.method() |
michael@0 | 172 | Traceback (most recent call last): |
michael@0 | 173 | ... |
michael@0 | 174 | TypeError: <lambda>() takes at least 4 arguments (1 given) |
michael@0 | 175 | >>> instance.method(1, 2, 3) |
michael@0 | 176 | >>> instance.method.assert_called_with(instance, 1, 2, 3) |
michael@0 | 177 | |
michael@0 | 178 | When you use `mocksignature` on instance methods `self` isn't included (and we |
michael@0 | 179 | can set the `return_value` etc directly): |
michael@0 | 180 | |
michael@0 | 181 | .. doctest:: |
michael@0 | 182 | |
michael@0 | 183 | >>> class SomeClass(object): |
michael@0 | 184 | ... def method(self, a, b, c=None): |
michael@0 | 185 | ... pass |
michael@0 | 186 | ... |
michael@0 | 187 | >>> instance = SomeClass() |
michael@0 | 188 | >>> instance.method = mocksignature(instance.method) |
michael@0 | 189 | >>> instance.method.return_value = None |
michael@0 | 190 | >>> instance.method(1, 2, 3) |
michael@0 | 191 | >>> instance.method.assert_called_with(1, 2, 3) |
michael@0 | 192 | |
michael@0 | 193 | |
michael@0 | 194 | mocksignature with classes |
michael@0 | 195 | ~~~~~~~~~~~~~~~~~~~~~~~~~~ |
michael@0 | 196 | |
michael@0 | 197 | When used with a class `mocksignature` copies the signature of the `__init__` |
michael@0 | 198 | method. |
michael@0 | 199 | |
michael@0 | 200 | .. doctest:: |
michael@0 | 201 | |
michael@0 | 202 | >>> class Something(object): |
michael@0 | 203 | ... def __init__(self, foo, bar): |
michael@0 | 204 | ... pass |
michael@0 | 205 | ... |
michael@0 | 206 | >>> MockSomething = mocksignature(Something) |
michael@0 | 207 | >>> instance = MockSomething(10, 9) |
michael@0 | 208 | >>> assert instance is MockSomething.return_value |
michael@0 | 209 | >>> MockSomething.assert_called_with(10, 9) |
michael@0 | 210 | >>> MockSomething() |
michael@0 | 211 | Traceback (most recent call last): |
michael@0 | 212 | ... |
michael@0 | 213 | TypeError: <lambda>() takes at least 2 arguments (0 given) |
michael@0 | 214 | |
michael@0 | 215 | Because the object returned by `mocksignature` is a function rather than a |
michael@0 | 216 | `Mock` you lose the other capabilities of `Mock`, like dynamic attribute |
michael@0 | 217 | creation. |
michael@0 | 218 | |
michael@0 | 219 | |
michael@0 | 220 | mocksignature with callable objects |
michael@0 | 221 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
michael@0 | 222 | |
michael@0 | 223 | When used with a callable object `mocksignature` copies the signature of the |
michael@0 | 224 | `__call__` method. |
michael@0 | 225 | |
michael@0 | 226 | .. doctest:: |
michael@0 | 227 | |
michael@0 | 228 | >>> class Something(object): |
michael@0 | 229 | ... def __call__(self, spam, eggs): |
michael@0 | 230 | ... pass |
michael@0 | 231 | ... |
michael@0 | 232 | >>> something = Something() |
michael@0 | 233 | >>> mock_something = mocksignature(something) |
michael@0 | 234 | >>> result = mock_something(10, 9) |
michael@0 | 235 | >>> mock_something.assert_called_with(10, 9) |
michael@0 | 236 | >>> mock_something() |
michael@0 | 237 | Traceback (most recent call last): |
michael@0 | 238 | ... |
michael@0 | 239 | TypeError: <lambda>() takes at least 2 arguments (0 given) |
michael@0 | 240 | |
michael@0 | 241 | |
michael@0 | 242 | mocksignature argument to patch |
michael@0 | 243 | ------------------------------- |
michael@0 | 244 | |
michael@0 | 245 | `mocksignature` is available as a keyword argument to :func:`patch` or |
michael@0 | 246 | :func:`patch.object`. It can be used with functions / methods / classes and |
michael@0 | 247 | callable objects. |
michael@0 | 248 | |
michael@0 | 249 | .. doctest:: |
michael@0 | 250 | |
michael@0 | 251 | >>> class SomeClass(object): |
michael@0 | 252 | ... def method(self, a, b, c=None): |
michael@0 | 253 | ... pass |
michael@0 | 254 | ... |
michael@0 | 255 | >>> @patch.object(SomeClass, 'method', mocksignature=True) |
michael@0 | 256 | ... def test(mock_method): |
michael@0 | 257 | ... instance = SomeClass() |
michael@0 | 258 | ... mock_method.return_value = None |
michael@0 | 259 | ... instance.method(1, 2) |
michael@0 | 260 | ... mock_method.assert_called_with(instance, 1, 2, None) |
michael@0 | 261 | ... |
michael@0 | 262 | >>> test() |