[3.7] bpo-36871: Handle spec errors in assert_has_calls (GH-16364) (GH-16374)

Handle spec errors in assert_has_calls (GH-16005) (GH-16364)

The fix in PR 13261 handled the underlying issue about the spec for specific methods not being applied correctly, but it didn't fix the issue that was causing the misleading error message.

The code currently grabs a list of responses from _call_matcher (which may include exceptions). But it doesn't reach inside the list when checking if the result is an exception. This results in a misleading error message when one of the provided calls does not match the spec.

https://bugs.python.org/issue36871

Co-authored-by: Samuel Freilich <sfreilich@google.com>

(cherry picked from commit 1a17a054f6)
This commit is contained in:
Gregory P. Smith 2019-09-24 22:29:17 -07:00 committed by GitHub
parent 16c0f6df62
commit 4042e1afd2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 40 additions and 3 deletions

View File

@ -895,13 +895,20 @@ 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] expected = [self._call_matcher(c) for c in calls]
cause = expected if isinstance(expected, Exception) else None cause = next((e for e in expected if isinstance(e, Exception)), None)
all_calls = _CallList(self._call_matcher(c) for c in self.mock_calls) all_calls = _CallList(self._call_matcher(c) for c in self.mock_calls)
if not any_order: if not any_order:
if expected not in all_calls: if expected not in all_calls:
if cause is None:
problem = 'Calls not found.'
else:
problem = ('Error processing expected calls.\n'
'Errors: {}').format(
[e if isinstance(e, Exception) else None
for e in expected])
raise AssertionError( raise AssertionError(
'Calls not found.\nExpected: %r\n' '%s\nExpected: %r\nActual: %r' % (
'Actual: %r' % (_CallList(calls), self.mock_calls) problem, _CallList(calls), self.mock_calls)
) from cause ) from cause
return return

View File

@ -1,4 +1,5 @@
import copy import copy
import re
import sys import sys
import tempfile import tempfile
@ -1394,6 +1395,32 @@ class MockTest(unittest.TestCase):
mock.assert_has_calls(calls[:-1]) mock.assert_has_calls(calls[:-1])
mock.assert_has_calls(calls[:-1], any_order=True) mock.assert_has_calls(calls[:-1], any_order=True)
def test_assert_has_calls_not_matching_spec_error(self):
def f(x=None): pass
mock = Mock(spec=f)
mock(1)
with self.assertRaisesRegex(
AssertionError,
'^{}$'.format(
re.escape('Calls not found.\n'
'Expected: [call()]\n'
'Actual: [call(1)]'))) as cm:
mock.assert_has_calls([call()])
self.assertIsNone(cm.exception.__cause__)
with self.assertRaisesRegex(
AssertionError,
'^{}$'.format(
re.escape(
'Error processing expected calls.\n'
"Errors: [None, TypeError('too many positional arguments')]\n"
"Expected: [call(), call(1, 2)]\n"
'Actual: [call(1)]'))) as cm:
mock.assert_has_calls([call(), call(1, 2)])
self.assertIsInstance(cm.exception.__cause__, TypeError)
def test_assert_any_call(self): def test_assert_any_call(self):
mock = Mock() mock = Mock()

View File

@ -0,0 +1,3 @@
Improve error handling for the assert_has_calls method of mocks.
Fixed a bug where any errors encountered while binding the expected calls
to the mock's spec were silently swallowed, leading to misleading error output.