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

This commit is contained in:
Jon Dufresne 2019-02-20 03:16:10 -08:00 committed by Carlton Gibson
parent 7071f8f272
commit 7feddd878c
4 changed files with 60 additions and 6 deletions

View File

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

View File

@ -187,7 +187,13 @@ Templates
Tests 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 URLs
~~~~ ~~~~

View File

@ -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_encoder`` argument allows setting a custom JSON encoder for
the JSON serialization that's described in :meth:`post`. 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 Once you have a ``Client`` instance, you can call any of the following
methods: methods:
@ -476,6 +484,23 @@ Specifically, a ``Response`` object has the following attributes:
:attr:`~django.template.response.SimpleTemplateResponse.context_data` :attr:`~django.template.response.SimpleTemplateResponse.context_data`
may be a suitable alternative on responses with that attribute. 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) .. method:: json(**kwargs)
The body of the response, parsed as JSON. Extra keyword arguments are 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 Exceptions
---------- ----------
If you point the test client at a view that raises an exception, that exception If you point the test client at a view that raises an exception and
will be visible in the test case. You can then use a standard ``try ... except`` ``Client.raise_request_exception`` is ``True``, that exception will be visible
block or :meth:`~unittest.TestCase.assertRaises` to test for exceptions. 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 The only exceptions that are not visible to the test client are
:class:`~django.http.Http404`, :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 exceptions internally and converts them into the appropriate HTTP response
codes. In these cases, you can check ``response.status_code`` in your test. 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 Persistent state
---------------- ----------------

View File

@ -759,6 +759,20 @@ class ClientTest(TestCase):
with self.assertRaises(KeyError): with self.assertRaises(KeyError):
self.client.get("/broken_view/") 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): def test_mail_sending(self):
"Mail is redirected to a dummy outbox during test setup" "Mail is redirected to a dummy outbox during test setup"
response = self.client.get('/mail_sending_view/') response = self.client.get('/mail_sending_view/')