ExpectedException now more useful.

This commit is contained in:
Jonathan Lange
2011-07-28 10:58:13 +01:00
6 changed files with 66 additions and 20 deletions

4
NEWS
View File

@@ -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)

View File

@@ -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

View File

@@ -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.

View File

@@ -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

View File

@@ -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)),
]

View File

@@ -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')