Skip to content

Commit

Permalink
Fixed #29687 -- Allowed the test client to serialize list/tuple as JSON.
Browse files Browse the repository at this point in the history
  • Loading branch information
danpalmer authored and timgraham committed Aug 25, 2018
1 parent 08f788b commit e181666
Show file tree
Hide file tree
Showing 6 changed files with 33 additions and 15 deletions.
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ answer newbie questions, and generally made Django that much better:
Daniel Wiesmann <[email protected]>
Danilo Bargen
Dan Johnson <[email protected]>
Dan Palmer <[email protected]>
Dan Poirier <[email protected]>
Dan Stephenson <http://dan.io/>
Dan Watson <http://danwatson.net/>
Expand Down
6 changes: 3 additions & 3 deletions django/test/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -317,10 +317,10 @@ def _encode_data(self, data, content_type):

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):
Expand Down
4 changes: 4 additions & 0 deletions docs/releases/2.2.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
~~~~

Expand Down
14 changes: 10 additions & 4 deletions docs/topics/testing/tools.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -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``
Expand Down
19 changes: 13 additions & 6 deletions tests/test_client/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,14 +95,21 @@ def test_post(self):
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."""
Expand Down
4 changes: 2 additions & 2 deletions tests/test_client/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))


Expand Down

0 comments on commit e181666

Please sign in to comment.