Issue #17015: When it has a spec, a Mock object now inspects its signature when matching calls, so that arguments can be matched positionally or by name.

This commit is contained in:
Antoine Pitrou 2013-02-03 00:23:58 +01:00
parent 18b30ee88e
commit 5c64df70b5
6 changed files with 313 additions and 87 deletions

View file

@ -277,6 +277,20 @@ instantiate the class in those tests.
... ...
AttributeError: object has no attribute 'old_method' AttributeError: object has no attribute 'old_method'
Using a specification also enables a smarter matching of calls made to the
mock, regardless of whether some parameters were passed as positional or
named arguments::
>>> def f(a, b, c): pass
...
>>> mock = Mock(spec=f)
>>> mock(1, 2, 3)
<Mock name='mock()' id='140161580456576'>
>>> mock.assert_called_with(a=1, b=2, c=3)
If you want this smarter matching to also work with method calls on the mock,
you can use :ref:`auto-speccing <auto-speccing>`.
If you want a stronger form of specification that prevents the setting If you want a stronger form of specification that prevents the setting
of arbitrary attributes as well as the getting of them then you can use of arbitrary attributes as well as the getting of them then you can use
`spec_set` instead of `spec`. `spec_set` instead of `spec`.

View file

@ -264,7 +264,6 @@ the `new_callable` argument to `patch`.
<Mock name='mock.method()' id='...'> <Mock name='mock.method()' id='...'>
>>> mock.method.assert_called_with(1, 2, 3, test='wow') >>> mock.method.assert_called_with(1, 2, 3, test='wow')
.. method:: assert_called_once_with(*args, **kwargs) .. method:: assert_called_once_with(*args, **kwargs)
Assert that the mock was called exactly once and with the specified Assert that the mock was called exactly once and with the specified
@ -685,6 +684,27 @@ have to create a dictionary and unpack it using `**`:
... ...
KeyError KeyError
A callable mock which was created with a *spec* (or a *spec_set*) will
introspect the specification object's signature when matching calls to
the mock. Therefore, it can match the actual call's arguments regardless
of whether they were passed positionally or by name::
>>> def f(a, b, c): pass
...
>>> mock = Mock(spec=f)
>>> mock(1, 2, c=3)
<Mock name='mock()' id='140161580456576'>
>>> mock.assert_called_with(1, 2, 3)
>>> mock.assert_called_with(a=1, b=2, c=3)
This applies to :meth:`~Mock.assert_called_with`,
:meth:`~Mock.assert_called_once_with`, :meth:`~Mock.assert_has_calls` and
:meth:`~Mock.assert_any_call`. When :ref:`auto-speccing`, it will also
apply to method calls on the mock object.
.. versionchanged:: 3.4
Added signature introspection on specced and autospecced mock objects.
.. class:: PropertyMock(*args, **kwargs) .. class:: PropertyMock(*args, **kwargs)

View file

@ -27,7 +27,7 @@ __version__ = '1.0'
import inspect import inspect
import pprint import pprint
import sys import sys
from functools import wraps from functools import wraps, partial
BaseExceptions = (BaseException,) BaseExceptions = (BaseException,)
@ -66,55 +66,45 @@ DescriptorTypes = (
) )
def _getsignature(func, skipfirst, instance=False): def _get_signature_object(func, as_instance, eat_self):
if isinstance(func, type) and not instance: """
Given an arbitrary, possibly callable object, try to create a suitable
signature object.
Return a (reduced func, signature) tuple, or None.
"""
if isinstance(func, type) and not as_instance:
# If it's a type and should be modelled as a type, use __init__.
try: try:
func = func.__init__ func = func.__init__
except AttributeError: except AttributeError:
return return None
skipfirst = True # Skip the `self` argument in __init__
eat_self = True
elif not isinstance(func, FunctionTypes): elif not isinstance(func, FunctionTypes):
# for classes where instance is True we end up here too # If we really want to model an instance of the passed type,
# __call__ should be looked up, not __init__.
try: try:
func = func.__call__ func = func.__call__
except AttributeError: except AttributeError:
return return None
if eat_self:
sig_func = partial(func, None)
else:
sig_func = func
try: try:
argspec = inspect.getfullargspec(func) return func, inspect.signature(sig_func)
except TypeError: except ValueError:
# C function / method, possibly inherited object().__init__ # Certain callable types are not supported by inspect.signature()
return return None
regargs, varargs, varkw, defaults, kwonly, kwonlydef, ann = argspec
# instance methods and classmethods need to lose the self argument
if getattr(func, '__self__', None) is not None:
regargs = regargs[1:]
if skipfirst:
# this condition and the above one are never both True - why?
regargs = regargs[1:]
signature = inspect.formatargspec(
regargs, varargs, varkw, defaults,
kwonly, kwonlydef, ann, formatvalue=lambda value: "")
return signature[1:-1], func
def _check_signature(func, mock, skipfirst, instance=False): def _check_signature(func, mock, skipfirst, instance=False):
if not _callable(func): sig = _get_signature_object(func, instance, skipfirst)
if sig is None:
return return
func, sig = sig
result = _getsignature(func, skipfirst, instance) def checksig(_mock_self, *args, **kwargs):
if result is None: sig.bind(*args, **kwargs)
return
signature, func = result
# can't use self because "self" is common as an argument name
# unfortunately even not in the first place
src = "lambda _mock_self, %s: None" % signature
checksig = eval(src, {})
_copy_func_details(func, checksig) _copy_func_details(func, checksig)
type(mock)._mock_check_sig = checksig type(mock)._mock_check_sig = checksig
@ -166,15 +156,12 @@ def _set_signature(mock, original, instance=False):
return return
skipfirst = isinstance(original, type) skipfirst = isinstance(original, type)
result = _getsignature(original, skipfirst, instance) result = _get_signature_object(original, instance, skipfirst)
if result is None: if result is None:
# was a C function (e.g. object().__init__ ) that can't be mocked
return return
func, sig = result
signature, func = result def checksig(*args, **kwargs):
sig.bind(*args, **kwargs)
src = "lambda %s: None" % signature
checksig = eval(src, {})
_copy_func_details(func, checksig) _copy_func_details(func, checksig)
name = original.__name__ name = original.__name__
@ -368,7 +355,7 @@ class NonCallableMock(Base):
def __init__( def __init__(
self, spec=None, wraps=None, name=None, spec_set=None, self, spec=None, wraps=None, name=None, spec_set=None,
parent=None, _spec_state=None, _new_name='', _new_parent=None, parent=None, _spec_state=None, _new_name='', _new_parent=None,
**kwargs _spec_as_instance=False, _eat_self=None, **kwargs
): ):
if _new_parent is None: if _new_parent is None:
_new_parent = parent _new_parent = parent
@ -382,8 +369,10 @@ class NonCallableMock(Base):
if spec_set is not None: if spec_set is not None:
spec = spec_set spec = spec_set
spec_set = True spec_set = True
if _eat_self is None:
_eat_self = parent is not None
self._mock_add_spec(spec, spec_set) self._mock_add_spec(spec, spec_set, _spec_as_instance, _eat_self)
__dict__['_mock_children'] = {} __dict__['_mock_children'] = {}
__dict__['_mock_wraps'] = wraps __dict__['_mock_wraps'] = wraps
@ -428,20 +417,26 @@ class NonCallableMock(Base):
self._mock_add_spec(spec, spec_set) self._mock_add_spec(spec, spec_set)
def _mock_add_spec(self, spec, spec_set): def _mock_add_spec(self, spec, spec_set, _spec_as_instance=False,
_eat_self=False):
_spec_class = None _spec_class = None
_spec_signature = None
if spec is not None and not _is_list(spec): if spec is not None and not _is_list(spec):
if isinstance(spec, type): if isinstance(spec, type):
_spec_class = spec _spec_class = spec
else: else:
_spec_class = _get_class(spec) _spec_class = _get_class(spec)
res = _get_signature_object(spec,
_spec_as_instance, _eat_self)
_spec_signature = res and res[1]
spec = dir(spec) spec = dir(spec)
__dict__ = self.__dict__ __dict__ = self.__dict__
__dict__['_spec_class'] = _spec_class __dict__['_spec_class'] = _spec_class
__dict__['_spec_set'] = spec_set __dict__['_spec_set'] = spec_set
__dict__['_spec_signature'] = _spec_signature
__dict__['_mock_methods'] = spec __dict__['_mock_methods'] = spec
@ -695,7 +690,6 @@ class NonCallableMock(Base):
self._mock_children[name] = _deleted self._mock_children[name] = _deleted
def _format_mock_call_signature(self, args, kwargs): def _format_mock_call_signature(self, args, kwargs):
name = self._mock_name or 'mock' name = self._mock_name or 'mock'
return _format_call_signature(name, args, kwargs) return _format_call_signature(name, args, kwargs)
@ -711,6 +705,28 @@ class NonCallableMock(Base):
return message % (expected_string, actual_string) return message % (expected_string, actual_string)
def _call_matcher(self, _call):
"""
Given a call (or simply a (args, kwargs) tuple), return a
comparison key suitable for matching with other calls.
This is a best effort method which relies on the spec's signature,
if available, or falls back on the arguments themselves.
"""
sig = self._spec_signature
if sig is not None:
if len(_call) == 2:
name = ''
args, kwargs = _call
else:
name, args, kwargs = _call
try:
return name, sig.bind(*args, **kwargs)
except TypeError as e:
return e.with_traceback(None)
else:
return _call
def assert_called_with(_mock_self, *args, **kwargs): def assert_called_with(_mock_self, *args, **kwargs):
"""assert that the mock was called with the specified arguments. """assert that the mock was called with the specified arguments.
@ -721,9 +737,14 @@ class NonCallableMock(Base):
expected = self._format_mock_call_signature(args, kwargs) expected = self._format_mock_call_signature(args, kwargs)
raise AssertionError('Expected call: %s\nNot called' % (expected,)) raise AssertionError('Expected call: %s\nNot called' % (expected,))
if self.call_args != (args, kwargs): def _error_message():
msg = self._format_mock_failure_message(args, kwargs) msg = self._format_mock_failure_message(args, kwargs)
raise AssertionError(msg) return msg
expected = self._call_matcher((args, kwargs))
actual = self._call_matcher(self.call_args)
if expected != actual:
cause = expected if isinstance(expected, Exception) else None
raise AssertionError(_error_message()) from cause
def assert_called_once_with(_mock_self, *args, **kwargs): def assert_called_once_with(_mock_self, *args, **kwargs):
@ -747,18 +768,21 @@ class NonCallableMock(Base):
If `any_order` is True then the calls can be in any order, but If `any_order` is True then the calls can be in any order, but
they must all appear in `mock_calls`.""" they must all appear in `mock_calls`."""
expected = [self._call_matcher(c) for c in calls]
cause = expected if isinstance(expected, Exception) else None
all_calls = _CallList(self._call_matcher(c) for c in self.mock_calls)
if not any_order: if not any_order:
if calls not in self.mock_calls: if expected not in all_calls:
raise AssertionError( raise AssertionError(
'Calls not found.\nExpected: %r\n' 'Calls not found.\nExpected: %r\n'
'Actual: %r' % (calls, self.mock_calls) 'Actual: %r' % (calls, self.mock_calls)
) ) from cause
return return
all_calls = list(self.mock_calls) all_calls = list(all_calls)
not_found = [] not_found = []
for kall in calls: for kall in expected:
try: try:
all_calls.remove(kall) all_calls.remove(kall)
except ValueError: except ValueError:
@ -766,7 +790,7 @@ class NonCallableMock(Base):
if not_found: if not_found:
raise AssertionError( raise AssertionError(
'%r not all found in call list' % (tuple(not_found),) '%r not all found in call list' % (tuple(not_found),)
) ) from cause
def assert_any_call(self, *args, **kwargs): def assert_any_call(self, *args, **kwargs):
@ -775,12 +799,14 @@ class NonCallableMock(Base):
The assert passes if the mock has *ever* been called, unlike The assert passes if the mock has *ever* been called, unlike
`assert_called_with` and `assert_called_once_with` that only pass if `assert_called_with` and `assert_called_once_with` that only pass if
the call is the most recent one.""" the call is the most recent one."""
kall = call(*args, **kwargs) expected = self._call_matcher((args, kwargs))
if kall not in self.call_args_list: actual = [self._call_matcher(c) for c in self.call_args_list]
if expected not in actual:
cause = expected if isinstance(expected, Exception) else None
expected_string = self._format_mock_call_signature(args, kwargs) expected_string = self._format_mock_call_signature(args, kwargs)
raise AssertionError( raise AssertionError(
'%s call not found' % expected_string '%s call not found' % expected_string
) ) from cause
def _get_child_mock(self, **kw): def _get_child_mock(self, **kw):
@ -850,11 +876,12 @@ class CallableMixin(Base):
self = _mock_self self = _mock_self
self.called = True self.called = True
self.call_count += 1 self.call_count += 1
self.call_args = _Call((args, kwargs), two=True)
self.call_args_list.append(_Call((args, kwargs), two=True))
_new_name = self._mock_new_name _new_name = self._mock_new_name
_new_parent = self._mock_new_parent _new_parent = self._mock_new_parent
_call = _Call((args, kwargs), two=True)
self.call_args = _call
self.call_args_list.append(_call)
self.mock_calls.append(_Call(('', args, kwargs))) self.mock_calls.append(_Call(('', args, kwargs)))
seen = set() seen = set()
@ -2031,6 +2058,8 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None,
elif spec is None: elif spec is None:
# None we mock with a normal mock without a spec # None we mock with a normal mock without a spec
_kwargs = {} _kwargs = {}
if _kwargs and instance:
_kwargs['_spec_as_instance'] = True
_kwargs.update(kwargs) _kwargs.update(kwargs)
@ -2097,10 +2126,12 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None,
if isinstance(spec, FunctionTypes): if isinstance(spec, FunctionTypes):
parent = mock.mock parent = mock.mock
new = MagicMock(parent=parent, name=entry, _new_name=entry,
_new_parent=parent, **kwargs)
mock._mock_children[entry] = new
skipfirst = _must_skip(spec, entry, is_type) skipfirst = _must_skip(spec, entry, is_type)
kwargs['_eat_self'] = skipfirst
new = MagicMock(parent=parent, name=entry, _new_name=entry,
_new_parent=parent,
**kwargs)
mock._mock_children[entry] = new
_check_signature(original, new, skipfirst=skipfirst) _check_signature(original, new, skipfirst=skipfirst)
# so functions created with _set_signature become instance attributes, # so functions created with _set_signature become instance attributes,
@ -2114,6 +2145,10 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None,
def _must_skip(spec, entry, is_type): def _must_skip(spec, entry, is_type):
"""
Return whether we should skip the first argument on spec's `entry`
attribute.
"""
if not isinstance(spec, type): if not isinstance(spec, type):
if entry in getattr(spec, '__dict__', {}): if entry in getattr(spec, '__dict__', {}):
# instance attribute - shouldn't skip # instance attribute - shouldn't skip
@ -2126,7 +2161,12 @@ def _must_skip(spec, entry, is_type):
continue continue
if isinstance(result, (staticmethod, classmethod)): if isinstance(result, (staticmethod, classmethod)):
return False return False
return is_type elif isinstance(getattr(result, '__get__', None), MethodWrapperTypes):
# Normal method => skip if looked up on type
# (if looked up on instance, self is already skipped)
return is_type
else:
return False
# shouldn't get here unless function is a dynamically provided attribute # shouldn't get here unless function is a dynamically provided attribute
# XXXX untested behaviour # XXXX untested behaviour
@ -2160,6 +2200,10 @@ FunctionTypes = (
type(ANY.__eq__), type(ANY.__eq__),
) )
MethodWrapperTypes = (
type(ANY.__eq__.__get__),
)
file_spec = None file_spec = None

View file

@ -337,9 +337,10 @@ class SpecSignatureTest(unittest.TestCase):
def test_basic(self): def test_basic(self):
for spec in (SomeClass, SomeClass()): mock = create_autospec(SomeClass)
mock = create_autospec(spec) self._check_someclass_mock(mock)
self._check_someclass_mock(mock) mock = create_autospec(SomeClass())
self._check_someclass_mock(mock)
def test_create_autospec_return_value(self): def test_create_autospec_return_value(self):
@ -576,10 +577,10 @@ class SpecSignatureTest(unittest.TestCase):
def test_spec_inheritance_for_classes(self): def test_spec_inheritance_for_classes(self):
class Foo(object): class Foo(object):
def a(self): def a(self, x):
pass pass
class Bar(object): class Bar(object):
def f(self): def f(self, y):
pass pass
class_mock = create_autospec(Foo) class_mock = create_autospec(Foo)
@ -587,26 +588,30 @@ class SpecSignatureTest(unittest.TestCase):
self.assertIsNot(class_mock, class_mock()) self.assertIsNot(class_mock, class_mock())
for this_mock in class_mock, class_mock(): for this_mock in class_mock, class_mock():
this_mock.a() this_mock.a(x=5)
this_mock.a.assert_called_with() this_mock.a.assert_called_with(x=5)
self.assertRaises(TypeError, this_mock.a, 'foo') this_mock.a.assert_called_with(5)
self.assertRaises(TypeError, this_mock.a, 'foo', 'bar')
self.assertRaises(AttributeError, getattr, this_mock, 'b') self.assertRaises(AttributeError, getattr, this_mock, 'b')
instance_mock = create_autospec(Foo()) instance_mock = create_autospec(Foo())
instance_mock.a() instance_mock.a(5)
instance_mock.a.assert_called_with() instance_mock.a.assert_called_with(5)
self.assertRaises(TypeError, instance_mock.a, 'foo') instance_mock.a.assert_called_with(x=5)
self.assertRaises(TypeError, instance_mock.a, 'foo', 'bar')
self.assertRaises(AttributeError, getattr, instance_mock, 'b') self.assertRaises(AttributeError, getattr, instance_mock, 'b')
# The return value isn't isn't callable # The return value isn't isn't callable
self.assertRaises(TypeError, instance_mock) self.assertRaises(TypeError, instance_mock)
instance_mock.Bar.f() instance_mock.Bar.f(6)
instance_mock.Bar.f.assert_called_with() instance_mock.Bar.f.assert_called_with(6)
instance_mock.Bar.f.assert_called_with(y=6)
self.assertRaises(AttributeError, getattr, instance_mock.Bar, 'g') self.assertRaises(AttributeError, getattr, instance_mock.Bar, 'g')
instance_mock.Bar().f() instance_mock.Bar().f(6)
instance_mock.Bar().f.assert_called_with() instance_mock.Bar().f.assert_called_with(6)
instance_mock.Bar().f.assert_called_with(y=6)
self.assertRaises(AttributeError, getattr, instance_mock.Bar(), 'g') self.assertRaises(AttributeError, getattr, instance_mock.Bar(), 'g')
@ -663,12 +668,15 @@ class SpecSignatureTest(unittest.TestCase):
self.assertRaises(TypeError, mock) self.assertRaises(TypeError, mock)
mock(1, 2) mock(1, 2)
mock.assert_called_with(1, 2) mock.assert_called_with(1, 2)
mock.assert_called_with(1, b=2)
mock.assert_called_with(a=1, b=2)
f.f = f f.f = f
mock = create_autospec(f) mock = create_autospec(f)
self.assertRaises(TypeError, mock.f) self.assertRaises(TypeError, mock.f)
mock.f(3, 4) mock.f(3, 4)
mock.f.assert_called_with(3, 4) mock.f.assert_called_with(3, 4)
mock.f.assert_called_with(a=3, b=4)
def test_skip_attributeerrors(self): def test_skip_attributeerrors(self):
@ -704,9 +712,13 @@ class SpecSignatureTest(unittest.TestCase):
self.assertRaises(TypeError, mock) self.assertRaises(TypeError, mock)
mock(1) mock(1)
mock.assert_called_once_with(1) mock.assert_called_once_with(1)
mock.assert_called_once_with(a=1)
self.assertRaises(AssertionError, mock.assert_called_once_with, 2)
mock(4, 5) mock(4, 5)
mock.assert_called_with(4, 5) mock.assert_called_with(4, 5)
mock.assert_called_with(a=4, b=5)
self.assertRaises(AssertionError, mock.assert_called_with, a=5, b=4)
def test_class_with_no_init(self): def test_class_with_no_init(self):
@ -719,24 +731,27 @@ class SpecSignatureTest(unittest.TestCase):
def test_signature_callable(self): def test_signature_callable(self):
class Callable(object): class Callable(object):
def __init__(self): def __init__(self, x, y):
pass pass
def __call__(self, a): def __call__(self, a):
pass pass
mock = create_autospec(Callable) mock = create_autospec(Callable)
mock() mock(1, 2)
mock.assert_called_once_with() mock.assert_called_once_with(1, 2)
mock.assert_called_once_with(x=1, y=2)
self.assertRaises(TypeError, mock, 'a') self.assertRaises(TypeError, mock, 'a')
instance = mock() instance = mock(1, 2)
self.assertRaises(TypeError, instance) self.assertRaises(TypeError, instance)
instance(a='a') instance(a='a')
instance.assert_called_once_with('a')
instance.assert_called_once_with(a='a') instance.assert_called_once_with(a='a')
instance('a') instance('a')
instance.assert_called_with('a') instance.assert_called_with('a')
instance.assert_called_with(a='a')
mock = create_autospec(Callable()) mock = create_autospec(Callable(1, 2))
mock(a='a') mock(a='a')
mock.assert_called_once_with(a='a') mock.assert_called_once_with(a='a')
self.assertRaises(TypeError, mock) self.assertRaises(TypeError, mock)
@ -779,7 +794,11 @@ class SpecSignatureTest(unittest.TestCase):
pass pass
a = create_autospec(Foo) a = create_autospec(Foo)
a.f(10)
a.f.assert_called_with(10)
a.f.assert_called_with(self=10)
a.f(self=10) a.f(self=10)
a.f.assert_called_with(10)
a.f.assert_called_with(self=10) a.f.assert_called_with(self=10)

View file

@ -25,6 +25,18 @@ class Iter(object):
__next__ = next __next__ = next
class Something(object):
def meth(self, a, b, c, d=None):
pass
@classmethod
def cmeth(cls, a, b, c, d=None):
pass
@staticmethod
def smeth(a, b, c, d=None):
pass
class MockTest(unittest.TestCase): class MockTest(unittest.TestCase):
@ -273,6 +285,43 @@ class MockTest(unittest.TestCase):
mock.assert_called_with(1, 2, 3, a='fish', b='nothing') mock.assert_called_with(1, 2, 3, a='fish', b='nothing')
def test_assert_called_with_function_spec(self):
def f(a, b, c, d=None):
pass
mock = Mock(spec=f)
mock(1, b=2, c=3)
mock.assert_called_with(1, 2, 3)
mock.assert_called_with(a=1, b=2, c=3)
self.assertRaises(AssertionError, mock.assert_called_with,
1, b=3, c=2)
# Expected call doesn't match the spec's signature
with self.assertRaises(AssertionError) as cm:
mock.assert_called_with(e=8)
self.assertIsInstance(cm.exception.__cause__, TypeError)
def test_assert_called_with_method_spec(self):
def _check(mock):
mock(1, b=2, c=3)
mock.assert_called_with(1, 2, 3)
mock.assert_called_with(a=1, b=2, c=3)
self.assertRaises(AssertionError, mock.assert_called_with,
1, b=3, c=2)
mock = Mock(spec=Something().meth)
_check(mock)
mock = Mock(spec=Something.cmeth)
_check(mock)
mock = Mock(spec=Something().cmeth)
_check(mock)
mock = Mock(spec=Something.smeth)
_check(mock)
mock = Mock(spec=Something().smeth)
_check(mock)
def test_assert_called_once_with(self): def test_assert_called_once_with(self):
mock = Mock() mock = Mock()
mock() mock()
@ -297,6 +346,29 @@ class MockTest(unittest.TestCase):
) )
def test_assert_called_once_with_function_spec(self):
def f(a, b, c, d=None):
pass
mock = Mock(spec=f)
mock(1, b=2, c=3)
mock.assert_called_once_with(1, 2, 3)
mock.assert_called_once_with(a=1, b=2, c=3)
self.assertRaises(AssertionError, mock.assert_called_once_with,
1, b=3, c=2)
# Expected call doesn't match the spec's signature
with self.assertRaises(AssertionError) as cm:
mock.assert_called_once_with(e=8)
self.assertIsInstance(cm.exception.__cause__, TypeError)
# Mock called more than once => always fails
mock(4, 5, 6)
self.assertRaises(AssertionError, mock.assert_called_once_with,
1, 2, 3)
self.assertRaises(AssertionError, mock.assert_called_once_with,
4, 5, 6)
def test_attribute_access_returns_mocks(self): def test_attribute_access_returns_mocks(self):
mock = Mock() mock = Mock()
something = mock.something something = mock.something
@ -991,6 +1063,39 @@ class MockTest(unittest.TestCase):
) )
def test_assert_has_calls_with_function_spec(self):
def f(a, b, c, d=None):
pass
mock = Mock(spec=f)
mock(1, b=2, c=3)
mock(4, 5, c=6, d=7)
mock(10, 11, c=12)
calls = [
('', (1, 2, 3), {}),
('', (4, 5, 6), {'d': 7}),
((10, 11, 12), {}),
]
mock.assert_has_calls(calls)
mock.assert_has_calls(calls, any_order=True)
mock.assert_has_calls(calls[1:])
mock.assert_has_calls(calls[1:], any_order=True)
mock.assert_has_calls(calls[:-1])
mock.assert_has_calls(calls[:-1], any_order=True)
# Reversed order
calls = list(reversed(calls))
with self.assertRaises(AssertionError):
mock.assert_has_calls(calls)
mock.assert_has_calls(calls, any_order=True)
with self.assertRaises(AssertionError):
mock.assert_has_calls(calls[1:])
mock.assert_has_calls(calls[1:], any_order=True)
with self.assertRaises(AssertionError):
mock.assert_has_calls(calls[:-1])
mock.assert_has_calls(calls[:-1], any_order=True)
def test_assert_any_call(self): def test_assert_any_call(self):
mock = Mock() mock = Mock()
mock(1, 2) mock(1, 2)
@ -1017,6 +1122,26 @@ class MockTest(unittest.TestCase):
) )
def test_assert_any_call_with_function_spec(self):
def f(a, b, c, d=None):
pass
mock = Mock(spec=f)
mock(1, b=2, c=3)
mock(4, 5, c=6, d=7)
mock.assert_any_call(1, 2, 3)
mock.assert_any_call(a=1, b=2, c=3)
mock.assert_any_call(4, 5, 6, 7)
mock.assert_any_call(a=4, b=5, c=6, d=7)
self.assertRaises(AssertionError, mock.assert_any_call,
1, b=3, c=2)
# Expected call doesn't match the spec's signature
with self.assertRaises(AssertionError) as cm:
mock.assert_any_call(e=8)
self.assertIsInstance(cm.exception.__cause__, TypeError)
def test_mock_calls_create_autospec(self): def test_mock_calls_create_autospec(self):
def f(a, b): def f(a, b):
pass pass

View file

@ -235,6 +235,10 @@ Core and Builtins
Library Library
------- -------
- Issue #17015: When it has a spec, a Mock object now inspects its signature
when matching calls, so that arguments can be matched positionally or
by name.
- Issue #15633: httplib.HTTPResponse is now mark closed when the server - Issue #15633: httplib.HTTPResponse is now mark closed when the server
sends less than the advertised Content-Length. sends less than the advertised Content-Length.