Kaydet (Commit) 47d94241 authored tarafından Petter Strandmark's avatar Petter Strandmark Kaydeden (comit) Victor Stinner

bpo-35047, unittest.mock: Better error messages on assert_called_xxx failures (GH-10090)

unittest.mock now includes mock calls in exception messages if
assert_not_called, assert_called_once, or assert_called_once_with
fails.
üst 18032632
...@@ -30,6 +30,7 @@ import pprint ...@@ -30,6 +30,7 @@ import pprint
import sys import sys
import builtins import builtins
from types import ModuleType from types import ModuleType
from unittest.util import safe_repr
from functools import wraps, partial from functools import wraps, partial
...@@ -778,8 +779,10 @@ class NonCallableMock(Base): ...@@ -778,8 +779,10 @@ class NonCallableMock(Base):
""" """
self = _mock_self self = _mock_self
if self.call_count != 0: if self.call_count != 0:
msg = ("Expected '%s' to not have been called. Called %s times." % msg = ("Expected '%s' to not have been called. Called %s times.%s"
(self._mock_name or 'mock', self.call_count)) % (self._mock_name or 'mock',
self.call_count,
self._calls_repr()))
raise AssertionError(msg) raise AssertionError(msg)
def assert_called(_mock_self): def assert_called(_mock_self):
...@@ -796,8 +799,10 @@ class NonCallableMock(Base): ...@@ -796,8 +799,10 @@ class NonCallableMock(Base):
""" """
self = _mock_self self = _mock_self
if not self.call_count == 1: if not self.call_count == 1:
msg = ("Expected '%s' to have been called once. Called %s times." % msg = ("Expected '%s' to have been called once. Called %s times.%s"
(self._mock_name or 'mock', self.call_count)) % (self._mock_name or 'mock',
self.call_count,
self._calls_repr()))
raise AssertionError(msg) raise AssertionError(msg)
def assert_called_with(_mock_self, *args, **kwargs): def assert_called_with(_mock_self, *args, **kwargs):
...@@ -825,8 +830,10 @@ class NonCallableMock(Base): ...@@ -825,8 +830,10 @@ class NonCallableMock(Base):
with the specified arguments.""" with the specified arguments."""
self = _mock_self self = _mock_self
if not self.call_count == 1: if not self.call_count == 1:
msg = ("Expected '%s' to be called once. Called %s times." % msg = ("Expected '%s' to be called once. Called %s times.%s"
(self._mock_name or 'mock', self.call_count)) % (self._mock_name or 'mock',
self.call_count,
self._calls_repr()))
raise AssertionError(msg) raise AssertionError(msg)
return self.assert_called_with(*args, **kwargs) return self.assert_called_with(*args, **kwargs)
...@@ -847,8 +854,8 @@ class NonCallableMock(Base): ...@@ -847,8 +854,8 @@ class NonCallableMock(Base):
if not any_order: if not any_order:
if expected not in all_calls: if expected not in all_calls:
raise AssertionError( raise AssertionError(
'Calls not found.\nExpected: %r\n' 'Calls not found.\nExpected: %r%s'
'Actual: %r' % (_CallList(calls), self.mock_calls) % (_CallList(calls), self._calls_repr(prefix="Actual"))
) from cause ) from cause
return return
...@@ -909,6 +916,19 @@ class NonCallableMock(Base): ...@@ -909,6 +916,19 @@ class NonCallableMock(Base):
return klass(**kw) return klass(**kw)
def _calls_repr(self, prefix="Calls"):
"""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"\n{prefix}: {safe_repr(self.mock_calls)}."
def _try_iter(obj): def _try_iter(obj):
if obj is None: if obj is None:
......
import copy import copy
import re
import sys import sys
import tempfile import tempfile
...@@ -407,6 +408,14 @@ class MockTest(unittest.TestCase): ...@@ -407,6 +408,14 @@ class MockTest(unittest.TestCase):
lambda: mock.assert_called_once_with('bob', 'bar', baz=2) 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,
re.escape("Calls: [call(1), call(2)]"),
lambda: m.assert_called_once_with(2))
def test_assert_called_once_with_function_spec(self): def test_assert_called_once_with_function_spec(self):
def f(a, b, c, d=None): def f(a, b, c, d=None):
...@@ -1250,6 +1259,13 @@ class MockTest(unittest.TestCase): ...@@ -1250,6 +1259,13 @@ class MockTest(unittest.TestCase):
with self.assertRaises(AssertionError): with self.assertRaises(AssertionError):
m.hello.assert_not_called() m.hello.assert_not_called()
def test_assert_not_called_message(self):
m = Mock()
m(1, 2)
self.assertRaisesRegex(AssertionError,
re.escape("Calls: [call(1, 2)]"),
m.assert_not_called)
def test_assert_called(self): def test_assert_called(self):
m = Mock() m = Mock()
with self.assertRaises(AssertionError): with self.assertRaises(AssertionError):
...@@ -1271,6 +1287,20 @@ class MockTest(unittest.TestCase): ...@@ -1271,6 +1287,20 @@ class MockTest(unittest.TestCase):
with self.assertRaises(AssertionError): with self.assertRaises(AssertionError):
m.hello.assert_called_once() m.hello.assert_called_once()
def test_assert_called_once_message(self):
m = Mock()
m(1, 2)
m(3)
self.assertRaisesRegex(AssertionError,
re.escape("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.assertNotIn("Calls:", str(e.exception))
#Issue21256 printout of keyword args should be in deterministic order #Issue21256 printout of keyword args should be in deterministic order
def test_sorted_call_signature(self): def test_sorted_call_signature(self):
m = Mock() m = Mock()
......
...@@ -1570,6 +1570,7 @@ Daniel Stokes ...@@ -1570,6 +1570,7 @@ Daniel Stokes
Michael Stone Michael Stone
Serhiy Storchaka Serhiy Storchaka
Ken Stox Ken Stox
Petter Strandmark
Charalampos Stratakis Charalampos Stratakis
Dan Stromberg Dan Stromberg
Donald Stufft Donald Stufft
......
``unittest.mock`` now includes mock calls in exception messages if
``assert_not_called``, ``assert_called_once``, or ``assert_called_once_with``
fails. Patch by Petter Strandmark.
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment