How to apply integration tests to a Flask RESTful API

Question:

[As per https://stackoverflow.com/a/46369945/1021819, the title should refer to integration tests rather than unit tests]

Suppose I’d like to test the following Flask API (from here):

import flask
import flask_restful

app = flask.Flask(__name__)
api = flask_restful.Api(app)

class HelloWorld(flask_restful.Resource):
    def get(self):
        return {'hello': 'world'}

api.add_resource(HelloWorld, '/')

if __name__ == "__main__":
    app.run(debug=True)

Having saved this as flaskapi.py and run it, in the same directory I run the script test_flaskapi.py:

import unittest
import flaskapi
import requests

class TestFlaskApiUsingRequests(unittest.TestCase):
    def test_hello_world(self):
        response = requests.get('http://localhost:5000')
        self.assertEqual(response.json(), {'hello': 'world'})


class TestFlaskApi(unittest.TestCase):
    def setUp(self):
        self.app = flaskapi.app.test_client()

    def test_hello_world(self):
        response = self.app.get('/')

if __name__ == "__main__":
    unittest.main()

Both the tests pass, but for the second test (defined in the TestFlaskApi) class I haven’t yet figured out how to assert that the JSON response is as expected (namely, {'hello': 'world'}). This is because it is an instance of flask.wrappers.Response (which is probably essentially a Werkzeug Response object (cf. http://werkzeug.pocoo.org/docs/0.11/wrappers/)), and I haven’t been able to find an equivalent of the json() method for requests Response object.

How can I make assertions on the JSON content of the second response?

Asked By: Kurt Peek

||

Answers:

I’ve found that I can get the JSON data by applying json.loads() to the output of the get_data() method:

import unittest
import flaskapi
import requests
import json
import sys

class TestFlaskApiUsingRequests(unittest.TestCase):
    def test_hello_world(self):
        response = requests.get('http://localhost:5000')
        self.assertEqual(response.json(), {'hello': 'world'})


class TestFlaskApi(unittest.TestCase):
    def setUp(self):
        self.app = flaskapi.app.test_client()

    def test_hello_world(self):
        response = self.app.get('/')
        self.assertEqual(
            json.loads(response.get_data().decode(sys.getdefaultencoding())), 
            {'hello': 'world'}
        )


if __name__ == "__main__":
    unittest.main()

Both tests pass as desired:

..
----------------------------------------------------------------------
Ran 2 tests in 0.019s

OK
[Finished in 0.3s]
Answered By: Kurt Peek

With Python3, I got the error TypeError: the JSON object must be str, not bytes. It is required to decode:

# in TestFlaskApi.test_hello_world
self.assertEqual(json.loads(response.get_data().decode()), {'hello': 'world'})

This question gives an explanation.

Answered By: pylipp

Flask provides a test_client you can use in your tests:

from source.api import app
from unittest import TestCase

class TestIntegrations(TestCase):
    def setUp(self):
        self.app = app.test_client()

    def test_thing(self):
        response = self.app.get('/')
        assert <make your assertion here>

Flask Testing Docs

Answered By: TheGrimmScientist

What you’re doing there is not unit testing. In every case, when using the requests library or the flask client, you’re doing integration testing as you make actual http calls to the endpoints and test the interaction.

Either the title of the question or the approach is not accurate.

Answered By: Luisa Emme

The response object from test_client has a get_json method.

There’s no need for converting the response to json with json.loads.

class TestFlaskApi(unittest.TestCase):
    def setUp(self):
        self.app = flaskapi.app.test_client()

    def test_hello_world(self):
        response = self.app.get("/")
        self.assertEqual(
            response.get_json(),
            {"hello": "world"},
        )
Answered By: Nizam Mohamed