summaryrefslogtreecommitdiffstats
path: root/Lib/unittest
diff options
context:
space:
mode:
authorSamuel Freilich <sfreilich@google.com>2019-09-24 19:08:31 (GMT)
committerMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>2019-09-24 19:08:31 (GMT)
commitb5a7a4f0c20717a4c92c371583b5521b83f40f32 (patch)
treef3f4df016736fca9503e1a8a6c28dc10222bbb43 /Lib/unittest
parentbb6bf7d342b4503a6227fd209fac934905b6a1aa (diff)
downloadcpython-b5a7a4f0c20717a4c92c371583b5521b83f40f32.zip
cpython-b5a7a4f0c20717a4c92c371583b5521b83f40f32.tar.gz
cpython-b5a7a4f0c20717a4c92c371583b5521b83f40f32.tar.bz2
bpo-36871: Handle spec errors in assert_has_calls (GH-16005)
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
Diffstat (limited to 'Lib/unittest')
-rw-r--r--Lib/unittest/mock.py26
-rw-r--r--Lib/unittest/test/testmock/testasync.py21
-rw-r--r--Lib/unittest/test/testmock/testmock.py19
3 files changed, 61 insertions, 5 deletions
diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py
index 22d63a4..7bd11c8 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 aca1cd0..f951526 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 ad67f98..88807d7 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()