summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDan Palmer <dan@danpalmer.me>2018-08-18 13:15:24 +0100
committerTim Graham <timograham@gmail.com>2018-08-25 10:57:05 -0400
commite1816669732c54c40122c8a22dcba42f6ee3c326 (patch)
tree80d66304077c63580144f72ba744d40a63d04c68
parent08f788b169a30d26f50433083aca253a4e4031b2 (diff)
Fixed #29687 -- Allowed the test client to serialize list/tuple as JSON.
-rw-r--r--AUTHORS1
-rw-r--r--django/test/client.py6
-rw-r--r--docs/releases/2.2.txt4
-rw-r--r--docs/topics/testing/tools.txt14
-rw-r--r--tests/test_client/tests.py19
-rw-r--r--tests/test_client/views.py4
6 files changed, 33 insertions, 15 deletions
diff --git a/AUTHORS b/AUTHORS
index 1da5bc4d87..3eebd6075e 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -206,6 +206,7 @@ answer newbie questions, and generally made Django that much better:
Daniel Wiesmann <daniel.wiesmann@gmail.com>
Danilo Bargen
Dan Johnson <danj.py@gmail.com>
+ Dan Palmer <dan@danpalmer.me>
Dan Poirier <poirier@pobox.com>
Dan Stephenson <http://dan.io/>
Dan Watson <http://danwatson.net/>
diff --git a/django/test/client.py b/django/test/client.py
index 4f826841cc..0845f0fa4b 100644
--- a/django/test/client.py
+++ b/django/test/client.py
@@ -317,10 +317,10 @@ class RequestFactory:
def _encode_json(self, data, content_type):
"""
- Return encoded JSON if data is a dict and content_type is
- application/json.
+ Return encoded JSON if data is a dict, list, or tuple and content_type
+ is application/json.
"""
- should_encode = JSON_CONTENT_TYPE_RE.match(content_type) and isinstance(data, dict)
+ should_encode = JSON_CONTENT_TYPE_RE.match(content_type) and isinstance(data, (dict, list, tuple))
return json.dumps(data, cls=self.json_encoder) if should_encode else data
def _get_path(self, parsed):
diff --git a/docs/releases/2.2.txt b/docs/releases/2.2.txt
index 4ca3bb9662..ff692151ed 100644
--- a/docs/releases/2.2.txt
+++ b/docs/releases/2.2.txt
@@ -224,6 +224,10 @@ Tests
URL, ignoring the ordering of the query string.
:meth:`~.SimpleTestCase.assertRedirects` uses the new assertion.
+* The test :class:`~.django.test.Client` now supports automatic JSON
+ serialization of list and tuple ``data`` when
+ ``content_type='application/json'``.
+
URLs
~~~~
diff --git a/docs/topics/testing/tools.txt b/docs/topics/testing/tools.txt
index 92e96e8c98..38b437e18c 100644
--- a/docs/topics/testing/tools.txt
+++ b/docs/topics/testing/tools.txt
@@ -213,10 +213,11 @@ Use the ``django.test.Client`` class to make requests.
name=fred&passwd=secret
- If you provide ``content_type`` as :mimetype:`application/json`, a
- ``data`` dictionary is serialized using :func:`json.dumps` with
- :class:`~django.core.serializers.json.DjangoJSONEncoder`. You can
- change the encoder by providing a ``json_encoder`` argument to
+ If you provide ``content_type`` as :mimetype:`application/json`, the
+ ``data`` is serialized using :func:`json.dumps` if it's a dict, list,
+ or tuple. Serialization is performed with
+ :class:`~django.core.serializers.json.DjangoJSONEncoder` by default,
+ and can be overridden by providing a ``json_encoder`` argument to
:class:`Client`. This serialization also happens for :meth:`put`,
:meth:`patch`, and :meth:`delete` requests.
@@ -226,6 +227,11 @@ Use the ``django.test.Client`` class to make requests.
you can call :func:`json.dumps` on ``data`` before passing it to
``post()`` to achieve the same thing.
+ .. versionchanged:: 2.2
+
+ The JSON serialization was extended to support lists and tuples. In
+ older versions, only dicts are serialized.
+
If you provide any other ``content_type`` (e.g. :mimetype:`text/xml`
for an XML payload), the contents of ``data`` are sent as-is in the
POST request, using ``content_type`` in the HTTP ``Content-Type``
diff --git a/tests/test_client/tests.py b/tests/test_client/tests.py
index b39d5f5e09..22e28be198 100644
--- a/tests/test_client/tests.py
+++ b/tests/test_client/tests.py
@@ -95,14 +95,21 @@ class ClientTest(TestCase):
def test_json_serialization(self):
"""The test client serializes JSON data."""
methods = ('post', 'put', 'patch', 'delete')
+ tests = (
+ ({'value': 37}, {'value': 37}),
+ ([37, True], [37, True]),
+ ((37, False), [37, False]),
+ )
for method in methods:
with self.subTest(method=method):
- client_method = getattr(self.client, method)
- method_name = method.upper()
- response = client_method('/json_view/', {'value': 37}, content_type='application/json')
- self.assertEqual(response.status_code, 200)
- self.assertEqual(response.context['data'], 37)
- self.assertContains(response, 'Viewing %s page.' % method_name)
+ for data, expected in tests:
+ with self.subTest(data):
+ client_method = getattr(self.client, method)
+ method_name = method.upper()
+ response = client_method('/json_view/', data, content_type='application/json')
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.context['data'], expected)
+ self.assertContains(response, 'Viewing %s page.' % method_name)
def test_json_encoder_argument(self):
"""The test Client accepts a json_encoder."""
diff --git a/tests/test_client/views.py b/tests/test_client/views.py
index 60a0b765d4..2d076fafaf 100644
--- a/tests/test_client/views.py
+++ b/tests/test_client/views.py
@@ -83,14 +83,14 @@ def post_view(request):
def json_view(request):
"""
A view that expects a request with the header 'application/json' and JSON
- data with a key named 'value'.
+ data, which is deserialized and included in the context.
"""
if request.META.get('CONTENT_TYPE') != 'application/json':
return HttpResponse()
t = Template('Viewing {} page. With data {{ data }}.'.format(request.method))
data = json.loads(request.body.decode('utf-8'))
- c = Context({'data': data['value']})
+ c = Context({'data': data})
return HttpResponse(t.render(c))