diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py index 22d63a45884..7bd11c8e703 100644 --- a/Lib/unittest/mock.py +++ b/Lib/unittest/mock.py @@ -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 diff --git a/Lib/unittest/test/testmock/testasync.py b/Lib/unittest/test/testmock/testasync.py index aca1cd0bbec..f951526dbeb 100644 --- a/Lib/unittest/test/testmock/testasync.py +++ b/Lib/unittest/test/testmock/testasync.py @@ -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) diff --git a/Lib/unittest/test/testmock/testmock.py b/Lib/unittest/test/testmock/testmock.py index ad67f98f87c..88807d71ccb 100644 --- a/Lib/unittest/test/testmock/testmock.py +++ b/Lib/unittest/test/testmock/testmock.py @@ -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 index 00000000000..6b7b19a0d57 --- /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 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.