From 9b258729f09e2df2574de6fa0299408d1a6042c3 Mon Sep 17 00:00:00 2001 From: Petter Strandmark Date: Thu, 25 Oct 2018 10:00:04 +0200 Subject: [PATCH 1/6] bpo-35047: Better error messages in unittest.mock When developing unit tests with `unittest.mock`, it is common to see error messages like this: AssertionError: Expected 'info' to not have been called. Called 3 times. This commit adds the actual list of calls to the error message. --- Lib/unittest/mock.py | 12 ++++++++---- Misc/ACKS | 1 + .../Library/2018-10-25-09-59-00.bpo-35047.abbaa.rst | 2 ++ 3 files changed, 11 insertions(+), 4 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2018-10-25-09-59-00.bpo-35047.abbaa.rst diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py index 6b7f293bc5e08c..a675501b3a51f7 100644 --- a/Lib/unittest/mock.py +++ b/Lib/unittest/mock.py @@ -778,8 +778,10 @@ def assert_not_called(_mock_self): """ self = _mock_self if self.call_count != 0: - msg = ("Expected '%s' to not have been called. Called %s times." % - (self._mock_name or 'mock', self.call_count)) + msg = ("Expected '%s' to not have been called. Called %s times.\n" + "Calls: %r." % (self._mock_name or 'mock', + self.call_count, + self.mock_calls)) raise AssertionError(msg) def assert_called(_mock_self): @@ -796,8 +798,10 @@ def assert_called_once(_mock_self): """ self = _mock_self if not self.call_count == 1: - msg = ("Expected '%s' to have been called once. Called %s times." % - (self._mock_name or 'mock', self.call_count)) + msg = ("Expected '%s' to have been called once. Called %s times.\n" + "Calls %r. " % (self._mock_name or 'mock', + self.call_count, + self.mock_calls)) raise AssertionError(msg) def assert_called_with(_mock_self, *args, **kwargs): diff --git a/Misc/ACKS b/Misc/ACKS index 5014584b7bc0b7..043d604a3f96a1 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -1570,6 +1570,7 @@ Daniel Stokes Michael Stone Serhiy Storchaka Ken Stox +Petter Strandmark Charalampos Stratakis Dan Stromberg Donald Stufft diff --git a/Misc/NEWS.d/next/Library/2018-10-25-09-59-00.bpo-35047.abbaa.rst b/Misc/NEWS.d/next/Library/2018-10-25-09-59-00.bpo-35047.abbaa.rst new file mode 100644 index 00000000000000..04aee21afc9bce --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-10-25-09-59-00.bpo-35047.abbaa.rst @@ -0,0 +1,2 @@ +``unittest.mock`` now prints the list of calls if ``assert_not_called`` +or ``assert_called_once`` fails. Patch by Petter Strandmark. \ No newline at end of file From 964e0551b31a46a5f6442acbed5e9db706302b93 Mon Sep 17 00:00:00 2001 From: Petter Strandmark Date: Sat, 27 Oct 2018 10:20:06 +0200 Subject: [PATCH 2/6] bpo-35047: Add helper method and unit tests. --- Lib/unittest/mock.py | 40 ++++++++++++++++++-------- Lib/unittest/test/testmock/testmock.py | 29 +++++++++++++++++++ 2 files changed, 57 insertions(+), 12 deletions(-) diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py index a675501b3a51f7..abbe863855f81c 100644 --- a/Lib/unittest/mock.py +++ b/Lib/unittest/mock.py @@ -30,6 +30,7 @@ import sys import builtins from types import ModuleType +from unittest.util import safe_repr from functools import wraps, partial @@ -778,10 +779,10 @@ def assert_not_called(_mock_self): """ self = _mock_self if self.call_count != 0: - msg = ("Expected '%s' to not have been called. Called %s times.\n" - "Calls: %r." % (self._mock_name or 'mock', - self.call_count, - self.mock_calls)) + msg = ("Expected '%s' to not have been called. Called %s times.%s" + % (self._mock_name or 'mock', + self.call_count, + self._call_list_as_string())) raise AssertionError(msg) def assert_called(_mock_self): @@ -798,10 +799,10 @@ def assert_called_once(_mock_self): """ self = _mock_self if not self.call_count == 1: - msg = ("Expected '%s' to have been called once. Called %s times.\n" - "Calls %r. " % (self._mock_name or 'mock', - self.call_count, - self.mock_calls)) + msg = ("Expected '%s' to have been called once. Called %s times.%s" + % (self._mock_name or 'mock', + self.call_count, + self._call_list_as_string())) raise AssertionError(msg) def assert_called_with(_mock_self, *args, **kwargs): @@ -829,8 +830,10 @@ def assert_called_once_with(_mock_self, *args, **kwargs): with the specified arguments.""" self = _mock_self if not self.call_count == 1: - msg = ("Expected '%s' to be called once. Called %s times." % - (self._mock_name or 'mock', self.call_count)) + msg = ("Expected '%s' to be called once. Called %s times.%s" + % (self._mock_name or 'mock', + self.call_count, + self._call_list_as_string())) raise AssertionError(msg) return self.assert_called_with(*args, **kwargs) @@ -851,8 +854,8 @@ def assert_has_calls(self, calls, any_order=False): if not any_order: if expected not in all_calls: raise AssertionError( - 'Calls not found.\nExpected: %r\n' - 'Actual: %r' % (_CallList(calls), self.mock_calls) + 'Calls not found.\nExpected: %r%s' + % (_CallList(calls), self._call_list_as_string()) ) from cause return @@ -913,6 +916,19 @@ def _get_child_mock(self, **kw): return klass(**kw) + def _call_list_as_string(self): + """Renders self.mock_calls as a string. + + Example: "\nCalls: [call(1), call(2)]." + + If self.mock_calls is empty, an empty string is returned. The + output will be truncated if very long. + """ + if not self.mock_calls: + return "" + return f"\nCalls: {safe_repr(self.mock_calls)}." + + def _try_iter(obj): if obj is None: diff --git a/Lib/unittest/test/testmock/testmock.py b/Lib/unittest/test/testmock/testmock.py index c7bfa277b511ca..18f14d255e6b83 100644 --- a/Lib/unittest/test/testmock/testmock.py +++ b/Lib/unittest/test/testmock/testmock.py @@ -407,6 +407,14 @@ def test_assert_called_once_with(self): lambda: mock.assert_called_once_with('bob', 'bar', baz=2) ) + def test_assert_called_once_with_call_list(self): + m = Mock() + m(1) + m(2) + self.assertRaisesRegex(AssertionError, + r"Calls: \[call\(1\), call\(2\)\]", + lambda: m.assert_called_once_with(2)) + def test_assert_called_once_with_function_spec(self): def f(a, b, c, d=None): @@ -1250,6 +1258,13 @@ def test_assert_not_called(self): with self.assertRaises(AssertionError): m.hello.assert_not_called() + def test_assert_not_called_message(self): + m = Mock() + m(1, 2) + self.assertRaisesRegex(AssertionError, + r"Calls: \[call\(1, 2\)\]", + m.assert_not_called) + def test_assert_called(self): m = Mock() with self.assertRaises(AssertionError): @@ -1271,6 +1286,20 @@ def test_assert_called_once(self): with self.assertRaises(AssertionError): m.hello.assert_called_once() + def test_assert_called_once_message(self): + m = Mock() + m(1, 2) + m(3) + self.assertRaisesRegex(AssertionError, + r"Calls: \[call\(1, 2\), call\(3\)\]", + m.assert_called_once) + + def test_assert_called_once_message_not_called(self): + m = Mock() + with self.assertRaises(AssertionError) as e: + m.assert_called_once() + self.assertFalse("Calls:" in str(e.exception)) + #Issue21256 printout of keyword args should be in deterministic order def test_sorted_call_signature(self): m = Mock() From 8e1ad7603f09d362b6adf561dbd17e63306f5073 Mon Sep 17 00:00:00 2001 From: Petter Strandmark Date: Sat, 27 Oct 2018 11:41:12 +0200 Subject: [PATCH 3/6] bpo-35047: Run make patchcheck. --- Lib/unittest/mock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py index abbe863855f81c..6562f3cd5080e2 100644 --- a/Lib/unittest/mock.py +++ b/Lib/unittest/mock.py @@ -922,7 +922,7 @@ def _call_list_as_string(self): Example: "\nCalls: [call(1), call(2)]." If self.mock_calls is empty, an empty string is returned. The - output will be truncated if very long. + output will be truncated if very long. """ if not self.mock_calls: return "" From a66130f5452f10d8022e83c3908cc272e123b71b Mon Sep 17 00:00:00 2001 From: Petter Strandmark Date: Sat, 27 Oct 2018 11:43:20 +0200 Subject: [PATCH 4/6] bpo-35047: Use assertNotIn. --- Lib/unittest/test/testmock/testmock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/unittest/test/testmock/testmock.py b/Lib/unittest/test/testmock/testmock.py index 18f14d255e6b83..205f3c565f3202 100644 --- a/Lib/unittest/test/testmock/testmock.py +++ b/Lib/unittest/test/testmock/testmock.py @@ -1298,7 +1298,7 @@ def test_assert_called_once_message_not_called(self): m = Mock() with self.assertRaises(AssertionError) as e: m.assert_called_once() - self.assertFalse("Calls:" in str(e.exception)) + self.assertNotIn("Calls:", str(e.exception)) #Issue21256 printout of keyword args should be in deterministic order def test_sorted_call_signature(self): From fb871dea1d0acd2fc28b1065209e123679fb831c Mon Sep 17 00:00:00 2001 From: Petter Strandmark Date: Sun, 28 Oct 2018 19:23:08 +0100 Subject: [PATCH 5/6] bpo-35047: Changes per review. - Rename helper function. Add optional argument. - Use re.compile. - Change NEWS entry. --- Lib/unittest/mock.py | 12 ++++++------ Lib/unittest/test/testmock/testmock.py | 7 ++++--- .../Library/2018-10-25-09-59-00.bpo-35047.abbaa.rst | 5 +++-- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py index 6562f3cd5080e2..420e928957421c 100644 --- a/Lib/unittest/mock.py +++ b/Lib/unittest/mock.py @@ -782,7 +782,7 @@ def assert_not_called(_mock_self): msg = ("Expected '%s' to not have been called. Called %s times.%s" % (self._mock_name or 'mock', self.call_count, - self._call_list_as_string())) + self._calls_repr())) raise AssertionError(msg) def assert_called(_mock_self): @@ -802,7 +802,7 @@ def assert_called_once(_mock_self): msg = ("Expected '%s' to have been called once. Called %s times.%s" % (self._mock_name or 'mock', self.call_count, - self._call_list_as_string())) + self._calls_repr())) raise AssertionError(msg) def assert_called_with(_mock_self, *args, **kwargs): @@ -833,7 +833,7 @@ def assert_called_once_with(_mock_self, *args, **kwargs): msg = ("Expected '%s' to be called once. Called %s times.%s" % (self._mock_name or 'mock', self.call_count, - self._call_list_as_string())) + self._calls_repr())) raise AssertionError(msg) return self.assert_called_with(*args, **kwargs) @@ -855,7 +855,7 @@ def assert_has_calls(self, calls, any_order=False): if expected not in all_calls: raise AssertionError( 'Calls not found.\nExpected: %r%s' - % (_CallList(calls), self._call_list_as_string()) + % (_CallList(calls), self._calls_repr(prefix="Actual")) ) from cause return @@ -916,7 +916,7 @@ def _get_child_mock(self, **kw): return klass(**kw) - def _call_list_as_string(self): + def _calls_repr(self, prefix="Calls"): """Renders self.mock_calls as a string. Example: "\nCalls: [call(1), call(2)]." @@ -926,7 +926,7 @@ def _call_list_as_string(self): """ if not self.mock_calls: return "" - return f"\nCalls: {safe_repr(self.mock_calls)}." + return f"\n{prefix}: {safe_repr(self.mock_calls)}." diff --git a/Lib/unittest/test/testmock/testmock.py b/Lib/unittest/test/testmock/testmock.py index 205f3c565f3202..8cd284a6522d61 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 @@ -412,7 +413,7 @@ def test_assert_called_once_with_call_list(self): m(1) m(2) self.assertRaisesRegex(AssertionError, - r"Calls: \[call\(1\), call\(2\)\]", + re.escape("Calls: [call(1), call(2)]"), lambda: m.assert_called_once_with(2)) @@ -1262,7 +1263,7 @@ def test_assert_not_called_message(self): m = Mock() m(1, 2) self.assertRaisesRegex(AssertionError, - r"Calls: \[call\(1, 2\)\]", + re.escape("Calls: [call(1, 2)]"), m.assert_not_called) def test_assert_called(self): @@ -1291,7 +1292,7 @@ def test_assert_called_once_message(self): m(1, 2) m(3) self.assertRaisesRegex(AssertionError, - r"Calls: \[call\(1, 2\), call\(3\)\]", + re.escape("Calls: [call(1, 2), call(3)]"), m.assert_called_once) def test_assert_called_once_message_not_called(self): diff --git a/Misc/NEWS.d/next/Library/2018-10-25-09-59-00.bpo-35047.abbaa.rst b/Misc/NEWS.d/next/Library/2018-10-25-09-59-00.bpo-35047.abbaa.rst index 04aee21afc9bce..72dca71923319a 100644 --- a/Misc/NEWS.d/next/Library/2018-10-25-09-59-00.bpo-35047.abbaa.rst +++ b/Misc/NEWS.d/next/Library/2018-10-25-09-59-00.bpo-35047.abbaa.rst @@ -1,2 +1,3 @@ -``unittest.mock`` now prints the list of calls if ``assert_not_called`` -or ``assert_called_once`` fails. Patch by Petter Strandmark. \ No newline at end of file +``unittest.mock`` now includes mock calls in exception messages if +``assert_not_called`` or ``assert_called_once`` fails. Patch by Petter +Strandmark. \ No newline at end of file From a34937499c7bbd6dd69233c8bc1ac0955932f4d7 Mon Sep 17 00:00:00 2001 From: Petter Strandmark Date: Sun, 28 Oct 2018 20:14:15 +0100 Subject: [PATCH 6/6] bpo-35047: List all three functions in the NEWS entry. --- .../next/Library/2018-10-25-09-59-00.bpo-35047.abbaa.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Misc/NEWS.d/next/Library/2018-10-25-09-59-00.bpo-35047.abbaa.rst b/Misc/NEWS.d/next/Library/2018-10-25-09-59-00.bpo-35047.abbaa.rst index 72dca71923319a..12eda27527d872 100644 --- a/Misc/NEWS.d/next/Library/2018-10-25-09-59-00.bpo-35047.abbaa.rst +++ b/Misc/NEWS.d/next/Library/2018-10-25-09-59-00.bpo-35047.abbaa.rst @@ -1,3 +1,3 @@ ``unittest.mock`` now includes mock calls in exception messages if -``assert_not_called`` or ``assert_called_once`` fails. Patch by Petter -Strandmark. \ No newline at end of file +``assert_not_called``, ``assert_called_once``, or ``assert_called_once_with`` +fails. Patch by Petter Strandmark. \ No newline at end of file