]> granicus.if.org Git - python/commitdiff
bpo-36871: Handle spec errors in assert_has_calls (GH-16005)
authorSamuel Freilich <sfreilich@google.com>
Tue, 24 Sep 2019 19:08:31 +0000 (15:08 -0400)
committerMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Tue, 24 Sep 2019 19:08:31 +0000 (12:08 -0700)
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

Automerge-Triggered-By: @gpshead
Lib/unittest/mock.py
Lib/unittest/test/testmock/testasync.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 22d63a45884a65213942230b1793714603209f61..7bd11c8e703412ec3a0b4fad56a8f6a71d052971 100644 (file)
@@ -926,13 +926,21 @@ 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%s'
-                    % (_CallList(calls), self._calls_repr(prefix="Actual"))
+                    f'{problem}\n'
+                    f'Expected: {_CallList(calls)}\n'
+                    f'Actual: {self._calls_repr(prefix="Actual")}'
                 ) from cause
             return
 
@@ -2244,12 +2252,20 @@ class AsyncMockMixin(Base):
         they must all appear in :attr:`await_args_list`.
         """
         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_awaits = _CallList(self._call_matcher(c) for c in self.await_args_list)
         if not any_order:
             if expected not in all_awaits:
+                if cause is None:
+                    problem = 'Awaits not found.'
+                else:
+                    problem = ('Error processing expected awaits.\n'
+                               'Errors: {}').format(
+                                   [e if isinstance(e, Exception) else None
+                                    for e in expected])
                 raise AssertionError(
-                    f'Awaits not found.\nExpected: {_CallList(calls)}\n'
+                    f'{problem}\n'
+                    f'Expected: {_CallList(calls)}\n'
                     f'Actual: {self.await_args_list}'
                 ) from cause
             return
index aca1cd0bbecc2bdc5e39ec3ee988bf27a2378d3b..f951526dbeb8a09785450ed3ab1b474f6d6eeab5 100644 (file)
@@ -1,5 +1,6 @@
 import asyncio
 import inspect
+import re
 import unittest
 
 from unittest.mock import (ANY, call, AsyncMock, patch, MagicMock,
@@ -889,3 +890,23 @@ class AsyncMockAssert(unittest.TestCase):
         asyncio.run(self._runnable_test())
         with self.assertRaises(AssertionError):
             self.mock.assert_not_awaited()
+
+    def test_assert_has_awaits_not_matching_spec_error(self):
+        async def f(): pass
+
+        mock = AsyncMock(spec=f)
+
+        with self.assertRaisesRegex(
+                AssertionError,
+                re.escape('Awaits not found.\nExpected:')) as cm:
+            mock.assert_has_awaits([call()])
+        self.assertIsNone(cm.exception.__cause__)
+
+        with self.assertRaisesRegex(
+                AssertionError,
+                re.escape('Error processing expected awaits.\n'
+                          "Errors: [None, TypeError('too many positional "
+                          "arguments')]\n"
+                          'Expected:')) as cm:
+            mock.assert_has_awaits([call(), call('wrong')])
+        self.assertIsInstance(cm.exception.__cause__, TypeError)
index ad67f98f87ccba47132767cc5f487b1103a00a9e..88807d71ccb06ceb3e8264212868526504ceb407 100644 (file)
@@ -1435,6 +1435,25 @@ 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(): pass
+
+        mock = Mock(spec=f)
+
+        with self.assertRaisesRegex(
+                AssertionError,
+                re.escape('Calls not found.\nExpected:')) as cm:
+            mock.assert_has_calls([call()])
+        self.assertIsNone(cm.exception.__cause__)
+
+        with self.assertRaisesRegex(
+                AssertionError,
+                re.escape('Error processing expected calls.\n'
+                          "Errors: [None, TypeError('too many positional "
+                          "arguments')]\n"
+                          'Expected:')) as cm:
+            mock.assert_has_calls([call(), call('wrong')])
+        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..6b7b19a
--- /dev/null
@@ -0,0 +1,3 @@
+Improve error handling for the assert_has_calls and assert_has_awaits methods 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.