Reputation: 463
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
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
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 :
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