Sending JSON using the django test client
DjangoJsonUnit TestingDjango 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.