[Fixed]-Django test Client submitting a form with a POST request

26👍

The key was to add content_type to the post method of client, and also urlencode the data.

from urllib import urlencode

...

data = urlencode({"something": "something"})
response = self.client.post("/my/form/", data, content_type="application/x-www-form-urlencoded")

Hope this helps someone!

3👍

If you are sending dictionaries on old-django versions using client, you must define the content_type=’application/json’ because its internal transformation fails to process dictionaries, you also need to send the dictionary like a blob using the json.dumps method. In conclusion, the following must work:

import json
from django.tests import TestCase

class MyTests(TestCase):
    def test_forms(self):
        response = self.client.post("/my/form/", json.dumps({'something':'something'}), content_type='application/json')

1👍

I have tried unit testing the POST requests in Django using Client(), but I fail to make it work (even with the methods specified above). So here is an alternative approach I take exclusively for the POST requests (using HttpRequest()):

from django.http import HttpRequest
from django.tests import TestCase
from . import views
# If a different test directory is being used to store the test files, replace the dot with the app name

class MyTests(TestCase):
    def test_forms(self):
        request = HttpRequest()
        request.method = 'POST'
        request.POST['something'] = 'something'
        request.META['HTTP_HOST'] = 'localhost'
        response = views.view_function_name(request)
        self.assertNotIn(b'Form error message', response.content)
        # make more assertions, if needed

Replace the view_function_name() with the actual function name. This function sends a POST request to the view being tested with the form-field ‘something’ and it’s corresponding value. The assertion statements would totally depend on the utility of the test functions, however.
Here are some assertions that may be used:

  • self.assertEquals(response.status_code, 302):
    Make this assertion when the form, upon submission of the POST request, redirects (302 is the status code for redirection). Read more about it here.
  • self.assertNotIn(b'Form error message', response.content):
    Replace ‘Form error message’ with the error message that the form generates when incorrect details are sent through the request. The test would fail if the test data is incorrect (the text is converted to bytes since HttpResponse().content is a bytes object as well).

If the view function uses the Django Message framework for displaying the form error messages as well, include this before the response:

from django.contrib import messages
...
request._messages = messages.storage.default_storage(request)

If the view function uses Sessions, include this before the response:

from importlib import import_module
from django.conf import settings
...
engine = import_module(settings.SESSION_ENGINE)
session_key = None
request.session = engine.SessionStore(session_key)

Before sending out the request, remember the use of any context-processors that your application may use.

I personally find this method more intuitive (and functional). This seems to cover all possible test cases with regard to HTTP requests and forms as well.

I would also like to suggest that each unit test case could be broken down into separate components for increased coverage and discovering latent bugs in code, instead of clubbing all cases in a single test_forms().

This technique was mentioned by Harry J.W. Percival in his book Test-Driven Development with Python.

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.

👤Greg

Leave a comment