Sending JSON using the django test client

DjangoJsonUnit Testing

Django Problem Overview


I'm working on a django a project that will serve as the endpoint for a webhook. The webhook will POST some JSON data to my endpoint, which will then parse that data. I'm trying to write unit tests for it, but I'm not sure if I'm sending the JSON properly.

I keep getting "TypeError: string indices must be integers" in pipeline_endpoint

Here's the code:

# tests.py
from django.test import TestCase
from django.test.client import Client
import simplejson

class TestPipeline(TestCase):

    def setUp(self):
        """initialize the Django test client"""
        self.c = Client()
    
    def test_200(self):
        json_string = u'{"1": {"guid": "8a40135230f21bdb0130f21c255c0007", "portalId": 999, "email": "fake@email"}}'
        json_data = simplejson.loads(json_string)
        self.response = self.c.post('/pipeline-endpoint', json_data, content_type="application/json")
        self.assertEqual(self.response.status_code, "200")

and

# views.py
from pipeline.prospect import Prospect
import simplejson

def pipeline_endpoint(request):

    #get the data from the json object that came in
    prospects_json = simplejson.loads(request.raw_post_data)
    for p in prospects_json:
        prospect = {
            'email'          : p['email'],
            'hs_id'          : p['guid'],
            'portal'         : p['portalId'],
        }

Edit: whole traceback.

======================================================================
ERROR: test_200 (pipeline.tests.TestPipeline)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "F:\......\pipeline\tests.py", line 31, in test_200
    self.response = self.c.post('/pipeline-endpoint', json_string, content_type="application/json")
  File "C:\Python27\lib\site-packages\django\test\client.py", line 455, in post
    response = super(Client, self).post(path, data=data, content_type=content_type, **extra)
  File "C:\Python27\lib\site-packages\django\test\client.py", line 256, in post
    return self.request(**r)
  File "C:\Python27\lib\site-packages\django\core\handlers\base.py", line 111, in get_response
    response = callback(request, *callback_args, **callback_kwargs)
  File "F:\......\pipeline\views.py", line 18, in pipeline_endpoint
    'email'          : p['email'],
TypeError: string indices must be integers

----------------------------------------------------------------------
Ran 1 test in 0.095s

FAILED (errors=1)
Destroying test database for alias 'default'...

Django Solutions


Solution 1 - Django

@mrmagooey is right

def test_your_test(self):
    python_dict = {
        "1": {
            "guid": "8a40135230f21bdb0130f21c255c0007",
            "portalId": 999,
            "email": "fake@email"
        }
    }
    response = self.client.post('/pipeline-endpoint/',
                                json.dumps(python_dict),
                                content_type="application/json")

use json.dumps instead of json.loads

Solution 2 - Django

Try:

 self.client.generic('POST', '/url', json.dumps({'json': 'object'})

Solution 3 - Django

rest_framework's APIClient (which is the the default client_class in APITestCase) takes care of dumping dict to JSON and it sets proper content type by passing format='json'.

from rest_framework import status
from rest_framework.test import APIClient, APITestCase


class MyTestCase(APITestCase):
    url = '/url'

    def post(self, payload, url=None):
        """
        Helper to send an HTTP post.

        @param (dict) payload: request body

        @returns: response
        """
        if url is None:
            url = self.url

        return self.client.post(url, payload, format='json')

    def test_my_function(self):
        payload = {
            'key': 'value'
        }
        response = self.post(payload)
        self.assertEqual(response.status_code, status.HTTP_200_OK)

Solution 4 - Django

You can always use the HttpRequest.body which loads the raw request data. This way you can handle your own data processing.

c = Client()
json_str= json.dumps({"data": {"id": 1}})
c.post('/ajax/handler/', data= json_str, content_type='application/json',
                                         HTTP_X_REQUESTED_WITH='XMLHttpRequest')


def index(request):
    ....
    print json.loads(request.body)

Solution 5 - Django

Since Django 3.0:

> If you provide content_type as application/json, the data is > serialized using json.dumps() if it’s a dict, list, or tuple. > Serialization is performed with DjangoJSONEncoder by default, and can > be overridden by providing a json_encoder argument to Client. This > serialization also happens for put(), patch(), and delete() requests.

response = client.post(
    f'/customer/{customer.id}/edit',
    {'email': new_email},
    content_type='application/json'
)

Solution 6 - Django

You can user iteritems on dictionaries to loop

for index, p in prospects_json.iteritems():
  prospect={
    'email': p['email'],
  }

or alternatively

for index in prospect_json:
  prospect={
    'email': prospect_json[ index ]['email']
  }

Solution 7 - Django

Adding to Guillaume Vincent's answer, from Django 2.1 we no longer need to use json.dumps for passing the data.

> Changed in Django 2.1: The JSON serialization described above was added. In older versions, you can call json.dumps() on data before passing it to post() to achieve the same thing.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionHartley BrodyView Question on Stackoverflow
Solution 1 - DjangoGuillaume VincentView Answer on Stackoverflow
Solution 2 - DjangoChris LambView Answer on Stackoverflow
Solution 3 - DjangoDušan MaďarView Answer on Stackoverflow
Solution 4 - DjangoamertkaraView Answer on Stackoverflow
Solution 5 - DjangoCatalin TrifView Answer on Stackoverflow
Solution 6 - DjangoczarchaicView Answer on Stackoverflow
Solution 7 - DjangoAmit ChaudharyView Answer on Stackoverflow