diff --git a/django/test/client.py b/django/test/client.py index 1262e187cb3..c1b05c0c519 100644 --- a/django/test/client.py +++ b/django/test/client.py @@ -10,6 +10,7 @@ from io import BytesIO from django.apps import apps from django.conf import settings +from django.core import urlresolvers from django.core.handlers.base import BaseHandler from django.core.handlers.wsgi import WSGIRequest from django.core.signals import (request_started, request_finished, @@ -18,7 +19,7 @@ from django.db import close_old_connections from django.http import SimpleCookie, HttpRequest, QueryDict from django.template import TemplateDoesNotExist from django.test import signals -from django.utils.functional import curry +from django.utils.functional import curry, SimpleLazyObject from django.utils.encoding import force_bytes, force_str from django.utils.http import urlencode from django.utils.itercompat import is_iterable @@ -449,6 +450,10 @@ class Client(RequestFactory): response.templates = data.get("templates", []) response.context = data.get("context") + # Attach the ResolverMatch instance to the response + response.resolver_match = SimpleLazyObject( + lambda: urlresolvers.resolve(request['PATH_INFO'])) + # Flatten a single context. Not really necessary anymore thanks to # the __getattr__ flattening in ContextList, but has some edge-case # backwards-compatibility implications. diff --git a/docs/releases/1.8.txt b/docs/releases/1.8.txt index 86f9a122c2d..b4f385529d0 100644 --- a/docs/releases/1.8.txt +++ b/docs/releases/1.8.txt @@ -214,8 +214,11 @@ Tests * The new :meth:`~django.test.SimpleTestCase.assertJSONNotEqual` assertion allows you to test that two JSON fragments are not equal. -* Added the ability to preserve the test database by adding the :djadminopt:`--keepdb` - flag. +* Added the ability to preserve the test database by adding the + :djadminopt:`--keepdb` flag. + +* Added the :attr:`~django.test.Response.resolver_match` attribute to test + client responses. Validators ^^^^^^^^^^ diff --git a/docs/topics/testing/tools.txt b/docs/topics/testing/tools.txt index 1d55b2e3dcc..e6433b5235f 100644 --- a/docs/topics/testing/tools.txt +++ b/docs/topics/testing/tools.txt @@ -432,6 +432,25 @@ Specifically, a ``Response`` object has the following attributes: loaded from a file. (The name is a string such as ``'admin/index.html'``.) + .. attribute:: resolver_match + + .. versionadded:: 1.8 + + An instance of :class:`~django.core.urlresolvers.ResolverMatch` for the + response. You can use the + :attr:`~django.core.urlresolvers.ResolverMatch.func` attribute, for + example, to verify the view that served the response:: + + # my_view here is a function based view + self.assertEqual(response.resolver_match.func, my_view) + + # class based views need to be compared by name, as the functions + # generated by as_view() won't be equal + self.assertEqual(response.resolver_match.func.__name__, MyView.as_view().__name__) + + If the given URL is not found, accessing this attribute will raise a + :exc:`~django.core.urlresolvers.Resolver404` exception. + You can also use dictionary syntax on the response object to query the value of any settings in the HTTP headers. For example, you could determine the content type of a response using ``response['Content-Type']``. diff --git a/tests/test_client/tests.py b/tests/test_client/tests.py index b9c033fe153..a14d1ebef13 100644 --- a/tests/test_client/tests.py +++ b/tests/test_client/tests.py @@ -99,6 +99,29 @@ class ClientTest(TestCase): self.assertIn(key, response.wsgi_request.environ) self.assertEqual(response.wsgi_request.environ[key], value) + def test_response_resolver_match(self): + """ + The response contains a ResolverMatch instance. + """ + response = self.client.get('/header_view/') + self.assertTrue(hasattr(response, 'resolver_match')) + + def test_response_resolver_match_redirect_follow(self): + """ + The response ResolverMatch instance contains the correct + information when following redirects. + """ + response = self.client.get('/redirect_view/', follow=True) + self.assertEqual(response.resolver_match.url_name, 'get_view') + + def test_response_resolver_match_regular_view(self): + """ + The response ResolverMatch instance contains the correct + information when accessing a regular view. + """ + response = self.client.get('/get_view/') + self.assertEqual(response.resolver_match.url_name, 'get_view') + def test_raw_post(self): "POST raw data (with a content type) to a view" test_doc = """BlinkMalcolm Gladwell""" diff --git a/tests/test_client/urls.py b/tests/test_client/urls.py index 554c934fe73..d56e151b018 100644 --- a/tests/test_client/urls.py +++ b/tests/test_client/urls.py @@ -5,7 +5,7 @@ from . import views urlpatterns = [ - url(r'^get_view/$', views.get_view), + url(r'^get_view/$', views.get_view, name='get_view'), url(r'^post_view/$', views.post_view), url(r'^header_view/$', views.view_with_header), url(r'^raw_post_view/$', views.raw_post_view),