Doug Murphy
Doug Murphy

Reputation: 463

405 error when testing POST in django application that is fully functional

I'm developing a web application in django that uses form POSTs to put user-entered data in a database.

PROBLEM: Testing using self.client.post() in django.test.TestCase returns a 405 (request not allowed).

RULED OUT:

QUESTION: What could be causing the 405, when I expect a 302 (redirect)?

This is one of the tests failing in this way:

def test_process_one_match_in_title(self):
    #TODO: the server is refusing self.client.post requests in testing, although they work in the app proper.  Why?

    #create a user and an abstract
    this_abstract = Abstract.objects.get(pk=5)  #'hypoplasia' in both title and abstract text
    this_annotator = create_annotator("Joe")
    this_annotator.save()

    userMatchesJSON = "{'hypoplasia': 8}"

    resp = self.client.post(reverse('diseaseMatcherApp:abstractDetail', kwargs={'pk': this_abstract.id}),
                            {'inputSoFar': 'hypoplasia', 'abstract_pk': this_abstract.id, 'user.id': this_annotator.id,
                             'userMatches': userMatchesJSON})
    self.assertEqual(resp.status_code, 302)  #FAILS HERE; AssertionError: 405 != 302

Upvotes: 3

Views: 3075

Answers (2)

Lukasz Dynowski
Lukasz Dynowski

Reputation: 13630

We had the same problem, exactly as @theenglishway described it. Unfortunately, we could not find the source of the problem. But we found a solution that worked for us.

Solution

Basically, we stopped using APIClient from django rest_framework.test and we replaced it with APIRequestFactory.

from rest_framework.test import APIRequestFactory
from my_app.views import MyAPIsView


# Client for API Request Factory
request_factory = APIRequestFactory()

# Class based view that you want to test
view = MyAPIsView.as_view()

# Request object
request = request_factory.post('/your/post/endpoint/')

# Response obtained from request returned from view
response = view(request)

Upvotes: 2

theenglishway
theenglishway

Reputation: 271

This problem has gotten me mad and it's the only question regarding this topics that shows up when searching that problem. I can't say I have a proper fix but I've solved it for me, and I thought I might share the result of a few hours of debugging.

I had just the exact same problem as you, that is a POST request that doesn't work in a unit test while it works perfectly fine when the full application is running. What was even crazier in my case is that the test worked on my laptop but not on my desktop machine, with no environment difference (same virtual environment, and just the minor difference of having python3.5.3 VS python3.5.1)

The messages I got were :

  • "Method Not Allowed (POST): url/where/i/wanted/to/post" interlaced with the test method names (when run with ./manage.py test -v2)
  • An assertion error in the "test sum-up" : "AssertionError: False is not true : Response didn't redirect as expected: Response code was 405 (expected 302)"

With the following piece of code :

r = self.client.post(url, follow=True)
self.assertRedirects(r, expected_url)

(The URL I was posting to was handled by a class derived from a CreateView)

The first message was firing from this piece of code which was triggered by going through this line

handler = getattr(self, request.method.lower(), self.http_method_not_allowed)

... with request.method set to 'POST' ... and it turned out my CreateView didn't have any 'post' method when in test mode (which is very weird ... and obviously when run on my laptop the view had that function !), so Django was falling back on that 'http_method_not_allowed' instead.

Now, why didn't my CreateView have a 'POST' method ? I have not taken the time to go through the whole init process (which seems rather complicated), so I have no precise idea, but it turned out in my case that some URLs were simply not 'initialized' in my tests (which I could figure out by printing stuff in this function in urls/resolvers.py). And for instance, the URL that was supposed to handle the POST request I was indeed non-existent while running my tests ... so I guess that explains why my CreateView was not initialized properly

I finally found out that my problem came from using django-crudbuilder app for building my CRUD views, and it doesn't initialize properly when the database is empty (which is the case while running a test on a non-persistent database)

Not sure where that problem might come from in other cases, but if you run into this you might like to check whether all your URLs are properly "seen" by Django while running your tests (and if they aren't, try and figure out why).

(the Django source code I've linked is from the current master but it's just the same as the one I've been going through in the version I use which is the 1.11.7)

Upvotes: 3

Related Questions