I found a load of questions about people trying to post JSON to a Flask app, but none of those are quite the problem I have here.
I have a simple REST API, built using Flask and Flask-JWT, which is working fine through a browser, but I've hit a block with my unit tests. The app is an App Engine app and runs fine on the local dev server and on App Engine. I'm running my unit tests with python -m unittest <module>
.
I have a basic Flask app created using a simple factory:
def create_app(name=__name__, cfg):
app = Flask(name)
app.config.update(cfg)
CORS(app)
JWT(app, auth_handler, identity_handler)
@app.route('/api/v1/ping', methods=['GET'])
def handle_ping():
return jsonify({'message': 'pong'})
# other routes ...
In the above, JWT
expects an authentication_handler
, which I have implemented:
def auth_handler(identity, secret):
# validate identity and secret
return User(...)
My client is Angular.js so I am posting JSON to this endpoint. This is all working fine in a browser, or with curl:
$ curl -H "content-type:application/json" -d '{"username":"[email protected]","password":"crm114"}' http://localhost:8080/api/v1/auth
giving me a 200 OK
and an access token in the response.
My simple test cases go something like this:
conf = {
'DEBUG': False,
'TESTING': True,
'SECRET_KEY': 'MoveAlongTheresNothingToSeeHere',
'JWT_AUTH_URL_RULE': '/api/v1/auth'
}
class FlaskRouteTests(unittest.TestCase):
def setUp(self):
api = factory.create_app(__name__, conf)
self.app = api.test_client()
def tearDown(self):
pass
def testRoute(self):
# This test works just fine
resp = self.app.get('/api/v1/ping')
self.assertEqual(resp.status, "200 OK", "Status should be 200, was %s" % resp.status)
def testAuth(self):
# This test does not work
resp = self.app.post('http://localhost:8080/api/v1/auth',
data="{'username': '[email protected]', 'password': 'crm114'}",
content_type='application/json', charset='UTF-8')
self.assertEqual(resp.status, "200 OK", "Status should be 200, was %s" % resp.status)
The plain old GET
test (testRoute()
) works just fine but testAuth()
gives me a 400 Bad Request
and I can't figure out why.
If I look at the werkzeug environ
immediately before it sends the request to my Flask app, I see this:
{
'SERVER_PORT': '8080',
'SERVER_PROTOCOL': 'HTTP/1.1',
'SCRIPT_NAME': '',
'wsgi.input': <_io.BytesIO object at 0x111fa8230>,
'REQUEST_METHOD': 'POST',
'HTTP_HOST': 'localhost:8080',
'PATH_INFO': '/api/v1/auth',
'wsgi.multithread': False,
'QUERY_STRING': '',
'HTTP_CONTENT_TYPE': 'application/json',
'HTTP_CONTENT_LENGTH': '53',
'CONTENT_LENGTH': '53',
'wsgi.version': (1, 0),
'SERVER_NAME': 'localhost',
'wsgi.run_once': False,
'wsgi.errors': <open file '<stderr>', mode 'w' at 0x10fd2d1e0>,
'wsgi.multiprocess': False,
'flask._preserve_context': False,
'wsgi.url_scheme': 'http',
'CONTENT_TYPE': u'application/json'
}
So, the content-type is correctly set and if I read the contents of the wsgi.input
(BytesIO.getvalue()
) I see {'username': '[email protected]', 'password': 'crm114'}
The request seems to be failing somewhere before it even hits my auth_handler
, so somewhere in werkzeug or Flask.
EnvironBuilder
interprets the data as a str
and converts it to a BytesIO
stream - I don't know if this is expected.data="{'username': '[email protected]', 'password': 'password'}"
and as a dict: data={'username': '[email protected]', 'password': 'password'}
, but it seems to make no difference. So, my question:
How do I post JSON to an endpoint in a unit test with Flask.test_client()
? Am I missing something obvious?
You are not posting valid JSON:
data="{'username': '[email protected]', 'password': 'crm114'}",
Note the single quotes. JSON uses double quotes; the following would be valid JSON:
data='{"username": "[email protected]", "password": "crm114"}',
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With