ExpectedException now more useful.
This commit is contained in:
4
NEWS
4
NEWS
@@ -39,6 +39,10 @@ Improvements
|
||||
* Correctly display non-ASCII unicode output on terminals that claim to have a
|
||||
unicode encoding. (Martin [gz], #804122)
|
||||
|
||||
* ``ExpectedException`` now matches any exception of the given type by
|
||||
default, and also allows specifying a ``Matcher`` rather than a mere regular
|
||||
expression. (Jonathan Lange, #791889)
|
||||
|
||||
* ``FixtureSuite`` added, allows test suites to run with a given fixture.
|
||||
(Jonathan Lange)
|
||||
|
||||
|
||||
@@ -160,9 +160,10 @@ particular errors. ``ExpectedException`` does just that. For example::
|
||||
silly.square('orange')
|
||||
|
||||
The first argument to ``ExpectedException`` is the type of exception you
|
||||
expect to see raised. The second argument is an optional regular expression,
|
||||
if provided, the ``str()`` of the raised exception must match the regular
|
||||
expression.
|
||||
expect to see raised. The second argument is optional, and can be either a
|
||||
regular expression or a matcher. If it is a regular expression, the ``str()``
|
||||
of the raised exception must match the regular expression. If it is a matcher,
|
||||
then the raised exception object must match it.
|
||||
|
||||
|
||||
assertIn, assertNotIn
|
||||
|
||||
@@ -421,7 +421,7 @@ class MatchesException(Matcher):
|
||||
Matcher.__init__(self)
|
||||
self.expected = exception
|
||||
if istext(value_re):
|
||||
value_re = AfterPreproccessing(str, MatchesRegex(value_re))
|
||||
value_re = AfterPreproccessing(str, MatchesRegex(value_re), False)
|
||||
self.value_re = value_re
|
||||
self._is_instance = type(self.expected) not in classtypes()
|
||||
|
||||
@@ -824,9 +824,19 @@ class AfterPreprocessing(object):
|
||||
return AfterPreprocessing(_read, Equals(content))
|
||||
"""
|
||||
|
||||
def __init__(self, preprocessor, matcher):
|
||||
def __init__(self, preprocessor, matcher, annotate=True):
|
||||
"""Create an AfterPreprocessing matcher.
|
||||
|
||||
:param preprocessor: A function called with the matchee before
|
||||
matching.
|
||||
:param matcher: What to match the preprocessed matchee against.
|
||||
:param annotate: Whether or not to annotate the matcher with
|
||||
something explaining how we transformed the matchee. Defaults
|
||||
to True.
|
||||
"""
|
||||
self.preprocessor = preprocessor
|
||||
self.matcher = matcher
|
||||
self.annotate = annotate
|
||||
|
||||
def _str_preprocessor(self):
|
||||
if isinstance(self.preprocessor, types.FunctionType):
|
||||
@@ -839,9 +849,13 @@ class AfterPreprocessing(object):
|
||||
|
||||
def match(self, value):
|
||||
after = self.preprocessor(value)
|
||||
return Annotate(
|
||||
"after %s on %r" % (self._str_preprocessor(), value),
|
||||
self.matcher).match(after)
|
||||
if self.annotate:
|
||||
matcher = Annotate(
|
||||
"after %s on %r" % (self._str_preprocessor(), value),
|
||||
self.matcher)
|
||||
else:
|
||||
matcher = self.matcher
|
||||
return matcher.match(after)
|
||||
|
||||
# This is the old, deprecated. spelling of the name, kept for backwards
|
||||
# compatibility.
|
||||
|
||||
@@ -16,7 +16,6 @@ __all__ = [
|
||||
|
||||
import copy
|
||||
import itertools
|
||||
import re
|
||||
import sys
|
||||
import types
|
||||
import unittest
|
||||
@@ -29,6 +28,7 @@ from testtools.compat import advance_iterator
|
||||
from testtools.matchers import (
|
||||
Annotate,
|
||||
Equals,
|
||||
MatchesException,
|
||||
Is,
|
||||
Not,
|
||||
)
|
||||
@@ -754,7 +754,7 @@ class ExpectedException:
|
||||
exception is raised, an AssertionError will be raised.
|
||||
"""
|
||||
|
||||
def __init__(self, exc_type, value_re):
|
||||
def __init__(self, exc_type, value_re=None):
|
||||
"""Construct an `ExpectedException`.
|
||||
|
||||
:param exc_type: The type of exception to expect.
|
||||
@@ -772,9 +772,11 @@ class ExpectedException:
|
||||
raise AssertionError('%s not raised.' % self.exc_type.__name__)
|
||||
if exc_type != self.exc_type:
|
||||
return False
|
||||
if not re.match(self.value_re, str(exc_value)):
|
||||
raise AssertionError('"%s" does not match "%s".' %
|
||||
(str(exc_value), self.value_re))
|
||||
if self.value_re:
|
||||
matcher = MatchesException(self.exc_type, self.value_re)
|
||||
mismatch = matcher.match((exc_type, exc_value, traceback))
|
||||
if mismatch:
|
||||
raise AssertionError(mismatch.describe())
|
||||
return True
|
||||
|
||||
|
||||
|
||||
@@ -258,10 +258,8 @@ class TestMatchesExceptionTypeReInterface(TestCase, TestMatchersInterface):
|
||||
MatchesException(Exception, 'fo.'))
|
||||
]
|
||||
describe_examples = [
|
||||
# XXX: This is kind of a crappy message. Need to change
|
||||
# AfterPreproccessing.
|
||||
("'bar' does not match 'fo.': after %r on %r"
|
||||
% (str, error_bar[1]), error_bar, MatchesException(ValueError, "fo.")),
|
||||
("'bar' does not match 'fo.'",
|
||||
error_bar, MatchesException(ValueError, "fo.")),
|
||||
]
|
||||
|
||||
|
||||
@@ -736,9 +734,10 @@ class TestAfterPreprocessing(TestCase, TestMatchersInterface):
|
||||
]
|
||||
|
||||
describe_examples = [
|
||||
("1 != 0: after <function parity> on 2",
|
||||
2,
|
||||
("1 != 0: after <function parity> on 2", 2,
|
||||
AfterPreprocessing(parity, Equals(1))),
|
||||
("1 != 0", 2,
|
||||
AfterPreprocessing(parity, Equals(1), annotate=False)),
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -8,6 +8,11 @@ from testtools import (
|
||||
ExpectedException,
|
||||
TestCase,
|
||||
)
|
||||
from testtools.matchers import (
|
||||
AfterPreprocessing,
|
||||
Equals,
|
||||
)
|
||||
|
||||
|
||||
class TestExpectedException(TestCase):
|
||||
"""Test the ExpectedException context manager."""
|
||||
@@ -16,13 +21,30 @@ class TestExpectedException(TestCase):
|
||||
with ExpectedException(ValueError, 'tes.'):
|
||||
raise ValueError('test')
|
||||
|
||||
def test_pass_on_raise_matcher(self):
|
||||
with ExpectedException(
|
||||
ValueError, AfterPreprocessing(str, Equals('test'))):
|
||||
raise ValueError('test')
|
||||
|
||||
def test_raise_on_text_mismatch(self):
|
||||
try:
|
||||
with ExpectedException(ValueError, 'tes.'):
|
||||
raise ValueError('mismatch')
|
||||
except AssertionError:
|
||||
e = sys.exc_info()[1]
|
||||
self.assertEqual('"mismatch" does not match "tes.".', str(e))
|
||||
self.assertEqual("'mismatch' does not match 'tes.'", str(e))
|
||||
else:
|
||||
self.fail('AssertionError not raised.')
|
||||
|
||||
def test_raise_on_general_mismatch(self):
|
||||
matcher = AfterPreprocessing(str, Equals('test'))
|
||||
value_error = ValueError('mismatch')
|
||||
try:
|
||||
with ExpectedException(ValueError, matcher):
|
||||
raise value_error
|
||||
except AssertionError:
|
||||
e = sys.exc_info()[1]
|
||||
self.assertEqual(matcher.match(value_error).describe(), str(e))
|
||||
else:
|
||||
self.fail('AssertionError not raised.')
|
||||
|
||||
@@ -45,3 +67,7 @@ class TestExpectedException(TestCase):
|
||||
self.assertEqual('TypeError not raised.', str(e))
|
||||
else:
|
||||
self.fail('AssertionError not raised.')
|
||||
|
||||
def test_pass_on_raise_any_message(self):
|
||||
with ExpectedException(ValueError):
|
||||
raise ValueError('whatever')
|
||||
|
||||
Reference in New Issue
Block a user