Skip to content

Commit 4042e1a

Browse files
authored
[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 <[email protected]> (cherry picked from commit 1a17a05)
1 parent 16c0f6d commit 4042e1a

File tree

3 files changed

+40
-3
lines changed

3 files changed

+40
-3
lines changed

Lib/unittest/mock.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -895,13 +895,20 @@ def assert_has_calls(self, calls, any_order=False):
895895
If `any_order` is True then the calls can be in any order, but
896896
they must all appear in `mock_calls`."""
897897
expected = [self._call_matcher(c) for c in calls]
898-
cause = expected if isinstance(expected, Exception) else None
898+
cause = next((e for e in expected if isinstance(e, Exception)), None)
899899
all_calls = _CallList(self._call_matcher(c) for c in self.mock_calls)
900900
if not any_order:
901901
if expected not in all_calls:
902+
if cause is None:
903+
problem = 'Calls not found.'
904+
else:
905+
problem = ('Error processing expected calls.\n'
906+
'Errors: {}').format(
907+
[e if isinstance(e, Exception) else None
908+
for e in expected])
902909
raise AssertionError(
903-
'Calls not found.\nExpected: %r\n'
904-
'Actual: %r' % (_CallList(calls), self.mock_calls)
910+
'%s\nExpected: %r\nActual: %r' % (
911+
problem, _CallList(calls), self.mock_calls)
905912
) from cause
906913
return
907914

Lib/unittest/test/testmock/testmock.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import copy
2+
import re
23
import sys
34
import tempfile
45

@@ -1394,6 +1395,32 @@ def f(a, b, c, d=None):
13941395
mock.assert_has_calls(calls[:-1])
13951396
mock.assert_has_calls(calls[:-1], any_order=True)
13961397

1398+
def test_assert_has_calls_not_matching_spec_error(self):
1399+
def f(x=None): pass
1400+
1401+
mock = Mock(spec=f)
1402+
mock(1)
1403+
1404+
with self.assertRaisesRegex(
1405+
AssertionError,
1406+
'^{}$'.format(
1407+
re.escape('Calls not found.\n'
1408+
'Expected: [call()]\n'
1409+
'Actual: [call(1)]'))) as cm:
1410+
mock.assert_has_calls([call()])
1411+
self.assertIsNone(cm.exception.__cause__)
1412+
1413+
1414+
with self.assertRaisesRegex(
1415+
AssertionError,
1416+
'^{}$'.format(
1417+
re.escape(
1418+
'Error processing expected calls.\n'
1419+
"Errors: [None, TypeError('too many positional arguments')]\n"
1420+
"Expected: [call(), call(1, 2)]\n"
1421+
'Actual: [call(1)]'))) as cm:
1422+
mock.assert_has_calls([call(), call(1, 2)])
1423+
self.assertIsInstance(cm.exception.__cause__, TypeError)
13971424

13981425
def test_assert_any_call(self):
13991426
mock = Mock()
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Improve error handling for the assert_has_calls method of mocks.
2+
Fixed a bug where any errors encountered while binding the expected calls
3+
to the mock's spec were silently swallowed, leading to misleading error output.

0 commit comments

Comments
 (0)