summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Lib/unittest/mock.py13
-rw-r--r--Lib/unittest/test/testmock/testmock.py27
-rw-r--r--Misc/NEWS.d/next/Core and Builtins/2019-09-24-18-45-46.bpo-36871.p47knk.rst3
3 files changed, 40 insertions, 3 deletions
diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py
index b1ab8631a4..2b9e7f14a7 100644
--- a/Lib/unittest/mock.py
+++ b/Lib/unittest/mock.py
@@ -895,13 +895,20 @@ class NonCallableMock(Base):
If `any_order` is True then the calls can be in any order, but
they must all appear in `mock_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)
if not any_order:
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(
- 'Calls not found.\nExpected: %r\n'
- 'Actual: %r' % (_CallList(calls), self.mock_calls)
+ '%s\nExpected: %r\nActual: %r' % (
+ problem, _CallList(calls), self.mock_calls)
) from cause
return
diff --git a/Lib/unittest/test/testmock/testmock.py b/Lib/unittest/test/testmock/testmock.py
index 4e0f4694b7..3046d4cfe6 100644
--- a/Lib/unittest/test/testmock/testmock.py
+++ b/Lib/unittest/test/testmock/testmock.py
@@ -1,4 +1,5 @@
import copy
+import re
import sys
import tempfile
@@ -1394,6 +1395,32 @@ class MockTest(unittest.TestCase):
mock.assert_has_calls(calls[:-1])
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):
mock = Mock()
diff --git a/Misc/NEWS.d/next/Core and Builtins/2019-09-24-18-45-46.bpo-36871.p47knk.rst b/Misc/NEWS.d/next/Core and Builtins/2019-09-24-18-45-46.bpo-36871.p47knk.rst
new file mode 100644
index 0000000000..56ee0f43f5
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and Builtins/2019-09-24-18-45-46.bpo-36871.p47knk.rst
@@ -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.