michael@0: michael@0: michael@0: michael@0: michael@0: michael@0:
michael@0: michael@0: michael@0:Note
michael@0:Autospeccing, added in mock 0.8, is a more advanced version of michael@0: mocksignature and can be used for many of the same use cases.
michael@0:A problem with using mock objects to replace real objects in your tests is that michael@0: Mock can be too flexible. Your code can treat the mock objects in michael@0: any way and you have to manually check that they were called correctly. If your michael@0: code calls functions or methods with the wrong number of arguments then mocks michael@0: don’t complain.
michael@0:The solution to this is mocksignature, which creates functions with the michael@0: same signature as the original, but delegating to a mock. You can interrogate michael@0: the mock in the usual way to check it has been called with the right michael@0: arguments, but if it is called with the wrong number of arguments it will michael@0: raise a TypeError in the same way your production code would.
michael@0:Another advantage is that your mocked objects are real functions, which can michael@0: be useful when your code uses michael@0: inspect or depends on michael@0: functions being function objects.
michael@0:Create a new function with the same signature as func that delegates michael@0: to mock. If skipfirst is True the first argument is skipped, useful michael@0: for methods where self needs to be omitted from the new function.
michael@0:If you don’t pass in a mock then one will be created for you.
michael@0:Functions returned by mocksignature have many of the same attributes michael@0: and assert methods as a mock object.
michael@0:The mock is set as the mock attribute of the returned function for easy michael@0: access.
michael@0:mocksignature can also be used with classes. It copies the signature of michael@0: the __init__ method.
michael@0:When used with callable objects (instances) it copies the signature of the michael@0: __call__ method.
michael@0:mocksignature will work out if it is mocking the signature of a method on michael@0: an instance or a method on a class and do the “right thing” with the self michael@0: argument in both cases.
michael@0:Because of a limitation in the way that arguments are collected by functions michael@0: created by mocksignature they are always passed as positional arguments michael@0: (including defaults) and not keyword arguments.
michael@0:Although the objects returned by mocksignature api are real function objects, michael@0: they have much of the same api as the Mock class. This includes the michael@0: assert methods:
michael@0:>>> def func(a, b, c):
michael@0: ... pass
michael@0: ...
michael@0: >>> func2 = mocksignature(func)
michael@0: >>> func2.called
michael@0: False
michael@0: >>> func2.return_value = 3
michael@0: >>> func2(1, 2, 3)
michael@0: 3
michael@0: >>> func2.called
michael@0: True
michael@0: >>> func2.assert_called_once_with(1, 2, 3)
michael@0: >>> func2.assert_called_with(1, 2, 4)
michael@0: Traceback (most recent call last):
michael@0: ...
michael@0: AssertionError: Expected call: mock(1, 2, 4)
michael@0: Actual call: mock(1, 2, 3)
michael@0: >>> func2.call_count
michael@0: 1
michael@0: >>> func2.side_effect = IndexError
michael@0: >>> func2(4, 5, 6)
michael@0: Traceback (most recent call last):
michael@0: ...
michael@0: IndexError
michael@0:
The mock object that is being delegated to is available as the mock attribute michael@0: of the function created by mocksignature.
michael@0:>>> func2.mock.mock_calls
michael@0: [call(1, 2, 3), call(4, 5, 6)]
michael@0:
The methods and attributes available on functions returned by mocksignature michael@0: are:
michael@0:michael@0:michael@0:assert_any_call(), assert_called_once_with(), michael@0: assert_called_with(), assert_has_calls(), michael@0: call_args, call_args_list, michael@0: call_count, called, michael@0: method_calls, mock, mock_calls, michael@0: reset_mock(), return_value, and michael@0: side_effect.
>>> def function(a, b, c=None):
michael@0: ... pass
michael@0: ...
michael@0: >>> mock = Mock()
michael@0: >>> function = mocksignature(function, mock)
michael@0: >>> function()
michael@0: Traceback (most recent call last):
michael@0: ...
michael@0: TypeError: <lambda>() takes at least 2 arguments (0 given)
michael@0: >>> function.return_value = 'some value'
michael@0: >>> function(1, 2, 'foo')
michael@0: 'some value'
michael@0: >>> function.assert_called_with(1, 2, 'foo')
michael@0:
Note that arguments to functions created by mocksignature are always passed michael@0: in to the underlying mock by position even when called with keywords:
michael@0:>>> def function(a, b, c=None):
michael@0: ... pass
michael@0: ...
michael@0: >>> function = mocksignature(function)
michael@0: >>> function.return_value = None
michael@0: >>> function(1, 2)
michael@0: >>> function.assert_called_with(1, 2, None)
michael@0:
When you use mocksignature to replace a method on a class then self michael@0: will be included in the method signature - and you will need to include michael@0: the instance when you do your asserts.
michael@0:As a curious factor of the way Python (2) wraps methods fetched from a class, michael@0: we can get the return_value from a function set on a class, but we can’t michael@0: set it. We have to do this through the exposed mock attribute instead:
michael@0:>>> class SomeClass(object):
michael@0: ... def method(self, a, b, c=None):
michael@0: ... pass
michael@0: ...
michael@0: >>> SomeClass.method = mocksignature(SomeClass.method)
michael@0: >>> SomeClass.method.mock.return_value = None
michael@0: >>> instance = SomeClass()
michael@0: >>> instance.method()
michael@0: Traceback (most recent call last):
michael@0: ...
michael@0: TypeError: <lambda>() takes at least 4 arguments (1 given)
michael@0: >>> instance.method(1, 2, 3)
michael@0: >>> instance.method.assert_called_with(instance, 1, 2, 3)
michael@0:
When you use mocksignature on instance methods self isn’t included (and we michael@0: can set the return_value etc directly):
michael@0:>>> class SomeClass(object):
michael@0: ... def method(self, a, b, c=None):
michael@0: ... pass
michael@0: ...
michael@0: >>> instance = SomeClass()
michael@0: >>> instance.method = mocksignature(instance.method)
michael@0: >>> instance.method.return_value = None
michael@0: >>> instance.method(1, 2, 3)
michael@0: >>> instance.method.assert_called_with(1, 2, 3)
michael@0:
When used with a class mocksignature copies the signature of the __init__ michael@0: method.
michael@0:>>> class Something(object):
michael@0: ... def __init__(self, foo, bar):
michael@0: ... pass
michael@0: ...
michael@0: >>> MockSomething = mocksignature(Something)
michael@0: >>> instance = MockSomething(10, 9)
michael@0: >>> assert instance is MockSomething.return_value
michael@0: >>> MockSomething.assert_called_with(10, 9)
michael@0: >>> MockSomething()
michael@0: Traceback (most recent call last):
michael@0: ...
michael@0: TypeError: <lambda>() takes at least 2 arguments (0 given)
michael@0:
Because the object returned by mocksignature is a function rather than a michael@0: Mock you lose the other capabilities of Mock, like dynamic attribute michael@0: creation.
michael@0:When used with a callable object mocksignature copies the signature of the michael@0: __call__ method.
michael@0:>>> class Something(object):
michael@0: ... def __call__(self, spam, eggs):
michael@0: ... pass
michael@0: ...
michael@0: >>> something = Something()
michael@0: >>> mock_something = mocksignature(something)
michael@0: >>> result = mock_something(10, 9)
michael@0: >>> mock_something.assert_called_with(10, 9)
michael@0: >>> mock_something()
michael@0: Traceback (most recent call last):
michael@0: ...
michael@0: TypeError: <lambda>() takes at least 2 arguments (0 given)
michael@0:
mocksignature is available as a keyword argument to patch() or michael@0: patch.object(). It can be used with functions / methods / classes and michael@0: callable objects.
michael@0:>>> class SomeClass(object):
michael@0: ... def method(self, a, b, c=None):
michael@0: ... pass
michael@0: ...
michael@0: >>> @patch.object(SomeClass, 'method', mocksignature=True)
michael@0: ... def test(mock_method):
michael@0: ... instance = SomeClass()
michael@0: ... mock_method.return_value = None
michael@0: ... instance.method(1, 2)
michael@0: ... mock_method.assert_called_with(instance, 1, 2, None)
michael@0: ...
michael@0: >>> test()
michael@0: