From 4525a0c4669dccb3a4c5d865184f8bb8e4a89ec8 Mon Sep 17 00:00:00 2001 From: Andy McKay Date: Fri, 8 May 2015 22:33:26 -0700 Subject: [PATCH] Fixed #24773 -- Added a json() method on test client responses. --- django/test/client.py | 8 ++++++++ docs/releases/1.9.txt | 3 ++- docs/topics/testing/tools.txt | 14 ++++++++++++++ tests/test_client_regress/tests.py | 10 ++++++++++ tests/test_client_regress/urls.py | 1 + tests/test_client_regress/views.py | 6 +++++- 6 files changed, 40 insertions(+), 2 deletions(-) diff --git a/django/test/client.py b/django/test/client.py index 043bf884a1b..f8c2705f983 100644 --- a/django/test/client.py +++ b/django/test/client.py @@ -1,5 +1,6 @@ from __future__ import unicode_literals +import json import mimetypes import os import re @@ -473,6 +474,8 @@ class Client(RequestFactory): response.templates = data.get("templates", []) response.context = data.get("context") + response.json = curry(self._parse_json, response) + # Attach the ResolverMatch instance to the response response.resolver_match = SimpleLazyObject( lambda: urlresolvers.resolve(request['PATH_INFO'])) @@ -641,6 +644,11 @@ class Client(RequestFactory): logout(request) self.cookies = SimpleCookie() + def _parse_json(self, response, **extra): + if 'application/json' not in response.get('Content-Type'): + raise ValueError('Content-Type header is "{0}", not "application/json"'.format(response.get('Content-Type'))) + return json.loads(response.content.decode(), **extra) + def _handle_redirects(self, response, **extra): "Follows any redirects by requesting responses from the server using GET." diff --git a/docs/releases/1.9.txt b/docs/releases/1.9.txt index e36c4512ca0..5b58f79fc02 100644 --- a/docs/releases/1.9.txt +++ b/docs/releases/1.9.txt @@ -289,7 +289,8 @@ Requests and Responses Tests ^^^^^ -* ... +* Added the :meth:`json() ` method to test client + responses to give access to the response body as JSON. URLs ^^^^ diff --git a/docs/topics/testing/tools.txt b/docs/topics/testing/tools.txt index aa66afb1d9d..e2ad15d5b50 100644 --- a/docs/topics/testing/tools.txt +++ b/docs/topics/testing/tools.txt @@ -425,6 +425,20 @@ Specifically, a ``Response`` object has the following attributes: >>> response.context['name'] 'Arthur' + .. method:: json(**kwargs) + + .. versionadded:: 1.9 + + The body of the response, parsed as JSON. Extra keyword arguments are + passed to :func:`json.loads`. For example:: + + >>> response = client.get('/foo/') + >>> response.json()['name'] + 'Arthur' + + If the ``Content-Type`` header is not ``"application/json"``, then a + :exc:`ValueError` will be raised when trying to parse the response. + .. attribute:: request The request data that stimulated the response. diff --git a/tests/test_client_regress/tests.py b/tests/test_client_regress/tests.py index f47ae10b56e..efc44289cd8 100644 --- a/tests/test_client_regress/tests.py +++ b/tests/test_client_regress/tests.py @@ -1270,6 +1270,16 @@ class RequestMethodStringDataTests(SimpleTestCase): response = self.client.head('/body/', data='', content_type='application/json') self.assertEqual(response.content, b'') + def test_json(self): + response = self.client.get('/json_response/') + self.assertEqual(response.json(), {'key': 'value'}) + + def test_json_wrong_header(self): + response = self.client.get('/body/') + msg = 'Content-Type header is "text/html; charset=utf-8", not "application/json"' + with self.assertRaisesMessage(ValueError, msg): + self.assertEqual(response.json(), {'key': 'value'}) + @override_settings(ROOT_URLCONF='test_client_regress.urls',) class QueryStringTests(SimpleTestCase): diff --git a/tests/test_client_regress/urls.py b/tests/test_client_regress/urls.py index fd7c131797b..9821ea910d7 100644 --- a/tests/test_client_regress/urls.py +++ b/tests/test_client_regress/urls.py @@ -30,6 +30,7 @@ urlpatterns = [ url(r'^request_methods/$', views.request_methods_view), url(r'^check_unicode/$', views.return_unicode), url(r'^check_binary/$', views.return_undecodable_binary), + url(r'^json_response/$', views.return_json_response), url(r'^parse_unicode_json/$', views.return_json_file), url(r'^check_headers/$', views.check_headers), url(r'^check_headers_redirect/$', RedirectView.as_view(url='/check_headers/')), diff --git a/tests/test_client_regress/views.py b/tests/test_client_regress/views.py index 6046564ee4c..d21448cba11 100644 --- a/tests/test_client_regress/views.py +++ b/tests/test_client_regress/views.py @@ -3,7 +3,7 @@ import json from django.conf import settings from django.contrib.auth.decorators import login_required from django.core.serializers.json import DjangoJSONEncoder -from django.http import HttpResponse, HttpResponseRedirect +from django.http import HttpResponse, HttpResponseRedirect, JsonResponse from django.shortcuts import render_to_response from django.template import RequestContext from django.template.loader import render_to_string @@ -108,6 +108,10 @@ def return_undecodable_binary(request): ) +def return_json_response(request): + return JsonResponse({'key': 'value'}) + + def return_json_file(request): "A view that parses and returns a JSON string as a file." match = CONTENT_TYPE_RE.match(request.META['CONTENT_TYPE'])