July 13, 2015 Nicolas Girault 4 min read

You can find the source code written in this article in the flask-boilerplate that we use at Theodo.

Functionally Testing an API consists in calling its routes and checking that the response status and the content of the response are what you expect.

It is quite simple to set up functional tests for your app. Writing a bunch of HTTP requests in the language of you choice that call your app (that has previously been launched) will do the job.

However, with this approach, your app is a blackbox that can only be accessed from its doors (i.e. its URLs). Although the goal of functional tests is actually to handle the app as a blackbox, it is still convenient while testing an API to have access to its database and to be able to mock calls to other services (especially in a context of micro-services environment).

Moreover it is important that the tests remain independent from each other. In other words, if a resource is added into the database during a test, the next test should not have to deal with it. This is not easy to handle unless the whole app is relaunched before each test. Even if it is done, some tests require different fixtures. It would be tricky to handle.

With this first approach, our functional tests were getting more complex than the code they were testing. I would like to share how we improved our tests using the flask test client class.

You don't need to know about flask/python to understand the following snippets.

The API allows to post and get users. First we can write a route to get a user given its id:

from model import User @app . route ( '/user/<int:id>' , methods = [ 'GET' ] ) def get_user_by_id ( self , id ) : user = User . query . get ( id ) return user . json

This route can be tested with the flask test client class:

import unittest import json from server import app from model import db , User class TestUser ( unittest . TestCase ) : def setUp ( self ) : self . client = app . test_client ( ) db . create_all ( ) self . user = User ( email = 'joe@theodo.fr' , password = 'super-secret-password' ) db . session . add ( self . user ) db . session . commit ( ) def tearDown ( self ) : db . session . remove ( ) db . drop_all ( ) def test_get_user ( self ) : response = self . client . get ( '/user/%d' % self . user . id , ) self . assertEqual ( response . status_code , 200 ) user = json . loads ( response . data . decode ( 'utf-8' ) ) self . assertEqual ( user [ 'email' ] , 'joe@theodo.fr' ) if __name__ == '__main__' : unittest . main ( )

In these tests:

all tests are independent: the database objects are rebuilt and fixtures are inserted before each test.

we have access to the database via the db object during the tests. So if you test a 'POST' route, you can check that a resource has been successfuly added into the database.

Another benefit is that you can easily mock a call to another API. Let's improve our API: the get_user_by_id function will call an external API to check if the user is a superhero:

import requests def is_superhero ( email ) : """Call the superhero API to find out if this email belongs to a superhero.""" response = requests . get ( 'http://127.0.0.1:5001/superhero/%s' % email ) return response . status_code == 200

from client import superhero @app . route ( '/user/<int:id>' , methods = [ 'GET' ] ) def get_user_by_id ( self , id ) : user = User . query . get ( id ) user_json = user . json user_json [ 'is_superhero' ] = superhero . is_superhero ( user . email ) return user_json

To prevent the tests from depending on this external API, we can mock the client in our tests:

from mock import patch @patch ( 'client.superhero.is_superhero' ) def test_get_user ( self , is_superhero_mock ) : is_superhero_mock . return_value = True response = self . client . get ( '/user/%d' % self . user . id , ) self . assertEqual ( response . status_code , 200 ) user = json . loads ( response . data . decode ( 'utf-8' ) ) self . assertEqual ( user [ 'email' ] , 'joe@theodo.fr' ) self . assertEqual ( user [ 'is_superhero' ] , True )

To use this mock for all tests, the mock can be instantiated in the setUp method:

def setUp ( self ) : self . patcher = patch ( 'client.superhero.is_superhero' ) is_superhero_mock . return_value = True is_superhero_mock . start ( ) def tearDown ( self ) : is_superhero_mock . stop ( )

Conclusion

With the Flask test client, you can write functional tests, keep control over the database and mock external calls. Here is a flask boilerplate to help you get started with a flask API.