diff --git a/AUTHORS b/AUTHORS index 94f6044926..d3346d2a1d 100644 --- a/AUTHORS +++ b/AUTHORS @@ -53,6 +53,7 @@ answer newbie questions, and generally made Django that much better: Shannon -jj Behrens Esdras Beleza James Bennett + Ben Paul Bissex Simon Blanchard Andrew Brehaut diff --git a/django/contrib/auth/forms.py b/django/contrib/auth/forms.py index 7700ec7d7a..023f9b43be 100644 --- a/django/contrib/auth/forms.py +++ b/django/contrib/auth/forms.py @@ -4,6 +4,7 @@ from django.contrib.sites.models import Site from django.template import Context, loader from django.core import validators from django import oldforms +from django.utils.translation import gettext as _ class UserCreationForm(oldforms.Manipulator): "A form that creates a user, with no privileges, from the given username and password." diff --git a/django/test/client.py b/django/test/client.py index f66b180867..ca1a04e659 100644 --- a/django/test/client.py +++ b/django/test/client.py @@ -1,7 +1,9 @@ +import sys from cStringIO import StringIO from django.conf import settings from django.core.handlers.base import BaseHandler from django.core.handlers.wsgi import WSGIRequest +from django.core.signals import got_request_exception from django.dispatch import dispatcher from django.http import urlencode, SimpleCookie from django.test import signals @@ -100,6 +102,14 @@ class Client: self.defaults = defaults self.cookies = SimpleCookie() self.session = {} + self.exc_info = None + + def store_exc_info(self, *args, **kwargs): + """ + Utility method that can be used to store exceptions when they are + generated by a view. + """ + self.exc_info = sys.exc_info() def request(self, **request): """ @@ -128,6 +138,9 @@ class Client: on_template_render = curry(store_rendered_templates, data) dispatcher.connect(on_template_render, signal=signals.template_rendered) + # Capture exceptions created by the handler + dispatcher.connect(self.store_exc_info, signal=got_request_exception) + response = self.handler(environ) # Add any rendered template detail to the response @@ -142,6 +155,11 @@ class Client: else: setattr(response, detail, None) + # Look for a signalled exception and reraise it + if self.exc_info: + raise self.exc_info[1], None, self.exc_info[2] + + # Update persistent cookie and session data if response.cookies: self.cookies.update(response.cookies) diff --git a/docs/testing.txt b/docs/testing.txt index 9906749723..cab31ed63b 100644 --- a/docs/testing.txt +++ b/docs/testing.txt @@ -291,6 +291,17 @@ for testing purposes: .. _RFC2616: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html +Exceptions +~~~~~~~~~~ + +If you point the Test Client at a view that raises an exception, that exception +will be visible in the test case. You can then use a standard ``try...catch`` +block, or ``unittest.TestCase.assertRaises()`` to test for exceptions. + +The only exceptions that are not visible in a Test Case are ``Http404``, +``PermissionDenied`` and ``SystemExit``. Django catches these exceptions +internally and converts them into the appropriate HTTP responses codes. + Persistent state ~~~~~~~~~~~~~~~~ diff --git a/tests/modeltests/test_client/models.py b/tests/modeltests/test_client/models.py index b6c3fbd5b1..74f5e9700c 100644 --- a/tests/modeltests/test_client/models.py +++ b/tests/modeltests/test_client/models.py @@ -114,4 +114,14 @@ class ClientTest(unittest.TestCase): # Check that the session was modified self.assertEquals(self.client.session['tobacconist'], 'hovercraft') - \ No newline at end of file + + def test_view_with_exception(self): + "Request a page that is known to throw an error" + self.assertRaises(KeyError, self.client.get, "/test_client/broken_view/") + + #Try the same assertion, a different way + try: + self.client.get('/test_client/broken_view/') + self.fail('Should raise an error') + except KeyError: + pass diff --git a/tests/modeltests/test_client/urls.py b/tests/modeltests/test_client/urls.py index 5c77260c92..96da4ec34e 100644 --- a/tests/modeltests/test_client/urls.py +++ b/tests/modeltests/test_client/urls.py @@ -6,5 +6,6 @@ urlpatterns = patterns('', (r'^post_view/$', views.post_view), (r'^redirect_view/$', views.redirect_view), (r'^login_protected_view/$', views.login_protected_view), - (r'^session_view/$', views.session_view) + (r'^session_view/$', views.session_view), + (r'^broken_view/$', views.broken_view) ) diff --git a/tests/modeltests/test_client/views.py b/tests/modeltests/test_client/views.py index 406a814dec..b1f2739845 100644 --- a/tests/modeltests/test_client/views.py +++ b/tests/modeltests/test_client/views.py @@ -36,11 +36,13 @@ login_protected_view = login_required(login_protected_view) def session_view(request): "A view that modifies the session" - request.session['tobacconist'] = 'hovercraft' t = Template('This is a view that modifies the session.', name='Session Modifying View Template') c = Context() return HttpResponse(t.render(c)) - \ No newline at end of file + +def broken_view(request): + """A view which just raises an exception, simulating a broken view.""" + raise KeyError("Oops! Looks like you wrote some bad code.")