← Back to team overview

testtools-dev team mailing list archive

[Merge] lp:~abentley/testtools/expected-exception into lp:testtools

 

Aaron Bentley has proposed merging lp:~abentley/testtools/expected-exception into lp:testtools.

Requested reviews:
  Jonathan Lange (jml)

For more details, see:
https://code.launchpad.net/~abentley/testtools/expected-exception/+merge/46858

This introduces the ExpectedException context manager for nicer error handling.
The tests are split into a separate file because they use the with statement.
The exec line is used because future imports cannot be conditionally imported.
-- 
https://code.launchpad.net/~abentley/testtools/expected-exception/+merge/46858
Your team testtools developers is subscribed to branch lp:testtools.
=== modified file 'testtools/__init__.py'
--- testtools/__init__.py	2010-12-18 07:21:34 +0000
+++ testtools/__init__.py	2011-01-20 00:09:53 +0000
@@ -37,6 +37,7 @@
     )
 from testtools.testcase import (
     ErrorHolder,
+    ExpectedException,
     PlaceHolder,
     TestCase,
     clone_test_with_new_id,

=== modified file 'testtools/testcase.py'
--- testtools/testcase.py	2011-01-13 22:32:02 +0000
+++ testtools/testcase.py	2011-01-20 00:09:53 +0000
@@ -14,6 +14,7 @@
 
 import copy
 import itertools
+import re
 import sys
 import types
 import unittest
@@ -682,3 +683,24 @@
     def _id(obj):
         return obj
     return _id
+
+
+class ExpectedException:
+    """A context manager to handle expected exceptions."""
+
+    def __init__(self, exc_type, value_re):
+        self.exc_type = exc_type
+        self.value_re = value_re
+
+    def __enter__(self):
+        pass
+
+    def __exit__(self, exc_type, exc_value, traceback):
+        if exc_type is None:
+            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))
+        return True

=== modified file 'testtools/tests/test_testtools.py'
--- testtools/tests/test_testtools.py	2010-12-13 01:15:11 +0000
+++ testtools/tests/test_testtools.py	2011-01-20 00:09:53 +0000
@@ -30,6 +30,12 @@
     Python27TestResult,
     ExtendedTestResult,
     )
+try:
+    exec('from __future__ import with_statement')
+except SyntaxError:
+    pass
+else:
+    from test_with_with import *
 
 
 class TestPlaceHolder(TestCase):

=== added file 'testtools/tests/test_with_with.py'
--- testtools/tests/test_with_with.py	1970-01-01 00:00:00 +0000
+++ testtools/tests/test_with_with.py	2011-01-20 00:09:53 +0000
@@ -0,0 +1,40 @@
+from __future__ import with_statement
+
+from testtools import (
+    ExpectedException,
+    TestCase,
+    )
+
+class TestExpectedException(TestCase):
+    """Test the ExpectedException context manager."""
+
+    def test_pass_on_raise(self):
+        with ExpectedException(ValueError, 'tes.'):
+            raise ValueError('test')
+
+    def test_raise_on_text_mismatch(self):
+        try:
+            with ExpectedException(ValueError, 'tes.'):
+                raise ValueError('mismatch')
+        except AssertionError, e:
+            self.assertEqual('"mismatch" does not match "tes.".', str(e))
+        else:
+            self.fail('AssertionError not raised.')
+
+    def test_raise_on_error_mismatch(self):
+        try:
+            with ExpectedException(TypeError, 'tes.'):
+                raise ValueError('mismatch')
+        except ValueError, e:
+            self.assertEqual('mismatch', str(e))
+        else:
+            self.fail('ValueError not raised.')
+
+    def test_raise_if_no_exception(self):
+        try:
+            with ExpectedException(TypeError, 'tes.'):
+                pass
+        except AssertionError, e:
+            self.assertEqual('TypeError not raised.', str(e))
+        else:
+            self.fail('AssertionError not raised.')


Follow ups