Making POST requests work with Django tests
It is the second time at work that I spent some minutes wondering why I was not properly receiving POST arguments in a view when testing it from django.
Let's have a little more context, imagine a very simple Django view where you want to print the value of a POST parameter in the console and return it.
class MyView(views.APIView):
def post(self, request):
param = request.POST.get('param', None)
print(param)
return param
So I manually test this with curl:
curl -X POST -d 'param=fiesta' 'https://my.local.url/myview/'
It works, so now I just want to write a simple test using django test framework:
def test_my_view(self):
data = {
'param': 'fiesta'
}
response = self.client.post(reverse('my-view), data)
assertEqual(response, 'fiesta')
However, the assert fails and the print(param)
line always yields None
, while when I was testing it with curl the parameter was always properly received. How come is that?
Explanation
Turns out when you send data in curl without specifying the Content-Type header, by default it sends the data in the application/x-www-form-urlencoded
format. If you want to send json data in your request you have to set the -H "Content-Type: application/json"
header properly.
This means that the view is accessing the POST object of data only received in the application/x-www-form-urlencoded
format, but the django test client, while calling the post in the line:
response = self.client.post(reverse('my-view), data)
Is sending the data by default in json format, and thus the view is not able to access it.
Solution
There are two ways to overcome this:
- Changing the
content_type
parameter in all tests to sendapplication/x-www-form-urlencoded
data, like:
response = self.client.post(reverse('my-view), data, content_type='application/x-www-form-urlencoded')
- Accessing the raw data or the request instead of the form data of the POST object.
param = request.data.get('param', None)
Have fun!