Have you ever tried to test POST requests to an API written with Flask? Today I was going through an old code base writing unittests that should have been written eons ago when I ran into a snag. The issue stemmed when I tried manually setting the headers Content-Type.
But before we get to that, let me show you the skeleton of the Flask route I was testing:
@app.route('/someendpoint/' methods=['POST']) def some_endpoint(): """API endpoint for submitting data to :return: status code 405 - invalid JSON or invalid request type :return: status code 400 - unsupported Content-Type or invalid publisher :return: status code 201 - successful submission """ # Ensure post's Content-Type is supported if request.headers['content-type'] == 'application/json': # Ensure data is a valid JSON try: user_submission = json.loads(request.data) except ValueError: return Response(status=405) ... some magic stuff happens if everything_went_well: return Response(status=201) else: return Response(status=405) # User submitted an unsupported Content-Type else: return Response(status=400)
Ignoring the magic, everything seems in order. So, lets go ahead and write a quick unittest that posts an invalid json.
def test_invalid_JSON(self): """Test status code 405 from improper JSON on post to raw""" response = self.app.post('/someendpoint', data="This isn't a json... it's a string!") self.assertEqual(response.status_code, 405)
Cool, let’s run it!
Failure self.assertEqual(response.status_code, 405) AssertionError: 400 != 405
A quick glance at the code and I realize I’ve forgotten to set the headers Content-Type! Wait. How do I set the Content-Type? That’s a good question. After searching around for people who had run into similar problems this is what I came up with:
def test_invalid_JSON(self): """Test status code 405 from improper JSON on post to raw""" response = self.app.post('/someendpoint', data="This isn't a json... it's a string!", headers={'content-type':'application/json') self.assertEqual(response.status_code, 405)
Let’s try this again.
Failure self.assertEqual(response.status_code, 405) AssertionError: 400 != 405
Hmmm, okay. Next, I decided to inspect the request coming into the flask app and found something odd in request.headers:
Host: localhost Content-Length: 10 Content-Type:
Why is the Content-Type empty? Another search gave hinted at another possible solution. Why not just build the headers dict inline?
headers=dict(content-type='application/json') # But that's not right. We can't have '-' in a key.
By this point I’ve become agitated. Neither the Flask docs themselves nor various forums have been of much use. Then I stumbled across this.
Perhaps I missed something in the docs. Either way, I learned that you can hand the Content-Type as a parameter to the post method. It works and it looks much cleaner. Let’s revise my initial unittest accordinlgy:
def test_invalid_JSON(self): """Test status code 405 from improper JSON on post to raw""" response = self.app.post('/raw', data="not a json", content_type='application/json') self.assertEqual(response.status_code, 405)
And, let’s run this one last time and look at the request as it comes though.
Tests passed Host: localhost Content-Length: 10 Content-Type: application/json
Much better. Now, back to testing!
-H.