]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.7] bpo-36871: Handle spec errors in assert_has_calls (GH-16364) (GH-16374)
authorGregory P. Smith <greg@krypto.org>
Wed, 25 Sep 2019 05:29:17 +0000 (22:29 -0700)
committerGitHub <noreply@github.com>
Wed, 25 Sep 2019 05:29:17 +0000 (22:29 -0700)
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 1a17a054f6314ce29cd2632c28aeed317a615360)

Lib/unittest/mock.py
Lib/unittest/test/testmock/testmock.py
Misc/NEWS.d/next/Core and Builtins/2019-09-24-18-45-46.bpo-36871.p47knk.rst [new file with mode: 0644]

index b1ab8631a422ef3f2497c991bb6b83dfde2fc02f..2b9e7f14a7563586430dd4be3e6ded615516b53e 100644 (file)
@@ -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
 
index 4e0f4694b7711502598ebc8272194b74a2e4c670..3046d4cfe639385ffab4ea84f38322ce08dc7bca 100644 (file)
@@ -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 (file)
index 0000000..56ee0f4
--- /dev/null
@@ -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.