Browse Source

Fixed #18707 -- Added support for the test client to return 500 responses.

Jon Dufresne 6 years ago
parent
commit
7feddd878c
4 changed files with 60 additions and 6 deletions
  1. 5 2
      django/test/client.py
  2. 7 1
      docs/releases/3.0.txt
  3. 34 3
      docs/topics/testing/tools.txt
  4. 14 0
      tests/test_client/tests.py

+ 5 - 2
django/test/client.py

@@ -440,9 +440,10 @@ class Client(RequestFactory):
     contexts and templates produced by a view, rather than the
     HTML rendered to the end-user.
     """
-    def __init__(self, enforce_csrf_checks=False, **defaults):
+    def __init__(self, enforce_csrf_checks=False, raise_request_exception=True, **defaults):
         super().__init__(**defaults)
         self.handler = ClientHandler(enforce_csrf_checks)
+        self.raise_request_exception = raise_request_exception
         self.exc_info = None
 
     def store_exc_info(self, **kwargs):
@@ -497,10 +498,12 @@ class Client(RequestFactory):
             # exception data, then re-raise the signalled exception.
             # Also make sure that the signalled exception is cleared from
             # the local cache!
+            response.exc_info = self.exc_info
             if self.exc_info:
                 _, exc_value, _ = self.exc_info
                 self.exc_info = None
-                raise exc_value
+                if self.raise_request_exception:
+                    raise exc_value
 
             # Save the client and request that stimulated the response.
             response.client = self

+ 7 - 1
docs/releases/3.0.txt

@@ -187,7 +187,13 @@ Templates
 Tests
 ~~~~~
 
-* ...
+* The new test :class:`~django.test.Client` argument
+  ``raise_request_exception`` allows controlling whether or not exceptions
+  raised during the request should also be raised in the test. The value
+  defaults to ``True`` for backwards compatibility. If it is ``False`` and an
+  exception occurs, the test client will return a 500 response with the
+  attribute :attr:`~django.test.Response.exc_info`, a tuple providing
+  information of the exception that occurred.
 
 URLs
 ~~~~

+ 34 - 3
docs/topics/testing/tools.txt

@@ -128,6 +128,14 @@ Use the ``django.test.Client`` class to make requests.
     The ``json_encoder`` argument allows setting a custom JSON encoder for
     the JSON serialization that's described in :meth:`post`.
 
+    The ``raise_request_exception`` argument allows controlling whether or not
+    exceptions raised during the request should also be raised in the test.
+    Defaults to ``True``.
+
+    .. versionadded:: 3.0
+
+        The ``raise_request_exception`` argument was added.
+
     Once you have a ``Client`` instance, you can call any of the following
     methods:
 
@@ -476,6 +484,23 @@ Specifically, a ``Response`` object has the following attributes:
             :attr:`~django.template.response.SimpleTemplateResponse.context_data`
             may be a suitable alternative on responses with that attribute.
 
+    .. attribute:: exc_info
+
+        .. versionadded:: 3.0
+
+        A tuple of three values that provides information about the unhandled
+        exception, if any, that occurred during the view.
+
+        The values are (type, value, traceback), the same as returned by
+        Python's :func:`sys.exc_info`. Their meanings are:
+
+        - *type*: The type of the exception.
+        - *value*: The exception instance.
+        - *traceback*: A traceback object which encapsulates the call stack at
+          the point where the exception originally occurred.
+
+        If no exception occurred, then ``exc_info`` will be ``None``.
+
     .. method:: json(**kwargs)
 
         The body of the response, parsed as JSON. Extra keyword arguments are
@@ -544,9 +569,10 @@ content type of a response using ``response['Content-Type']``.
 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 ... except``
-block or :meth:`~unittest.TestCase.assertRaises` to test for exceptions.
+If you point the test client at a view that raises an exception and
+``Client.raise_request_exception`` is ``True``, that exception will be visible
+in the test case. You can then use a standard ``try ... except`` block or
+:meth:`~unittest.TestCase.assertRaises` to test for exceptions.
 
 The only exceptions that are not visible to the test client are
 :class:`~django.http.Http404`,
@@ -555,6 +581,11 @@ The only exceptions that are not visible to the test client are
 exceptions internally and converts them into the appropriate HTTP response
 codes. In these cases, you can check ``response.status_code`` in your test.
 
+If ``Client.raise_request_exception`` is ``False``, the test client will return a
+500 response as would be returned to a browser. The response has the attribute
+:attr:`~Response.exc_info` to provide information about the unhandled
+exception.
+
 Persistent state
 ----------------
 

+ 14 - 0
tests/test_client/tests.py

@@ -759,6 +759,20 @@ class ClientTest(TestCase):
         with self.assertRaises(KeyError):
             self.client.get("/broken_view/")
 
+    def test_exc_info(self):
+        client = Client(raise_request_exception=False)
+        response = client.get("/broken_view/")
+        self.assertEqual(response.status_code, 500)
+        exc_type, exc_value, exc_traceback = response.exc_info
+        self.assertIs(exc_type, KeyError)
+        self.assertIsInstance(exc_value, KeyError)
+        self.assertEqual(str(exc_value), "'Oops! Looks like you wrote some bad code.'")
+        self.assertIsNotNone(exc_traceback)
+
+    def test_exc_info_none(self):
+        response = self.client.get("/get_view/")
+        self.assertIsNone(response.exc_info)
+
     def test_mail_sending(self):
         "Mail is redirected to a dummy outbox during test setup"
         response = self.client.get('/mail_sending_view/')