FastAPI Testing
Posted on Sun 04 September 2022
Never thought, we'd see the day where we get an API first lean framework for Python. For years, Django-Rest and Flask-Rest have dominated this space. Now there is a, relatively, new contender, fresh and clean, FastAPI. There are numerous posts about the merits of each framework, which is the reason, the intention here is not to repeat. I believe people are competent enough to know when and where to use each framework and why, be it simply based preference.
Specifically, this article is going to discuss the testing features that supplement FastAPI development. In summary, it offers a nice and clean setup for ensuring that during development the APIs can be acceptably / cleanly and locally tested without having to worry about the infrastructure side of things.
For the sake of simplicity we shall look at a "Hello, World." API example. A single endpoint; root ('/') that returns "Hello, World.", as a result in the form of JSON.
In the first instance, the code would just be dumped into a single file and we shall clean it up in the 2nd iteration so that it is much more representative of what we'd have in a project. So we start with this:
from fastapi import FastAPI,
app = FastAPI()
@app.get('/')
def get():
return {'msg': 'Hello, World.'}
We then proceed to write a test for this simple API as:
from fastapi.testclient import TestClient
cl = TestClient(app)
def test_get():
rs = cl.get('/')
assert(rs.status_code == 200, 'Response status code is invalid.')
assert(rs.json() == {'msg': 'Hello, World.'}. 'Response is invalid.')
if __name__ == '__main__':
test_get()
Initially for the sake of experimentation one could have the whole source in the same file. The TestClient is a, infact a test client provided by FastAPI that facilitates the exercise of the implemented API code. The above example ofcourse is very simply however one can see how easy it is as a subsequent step to split and extend the API and the associated tests. So that we can setup an array of unit tests around our API. BUT first, one can now simply run the API test as:
>python api.py
If one wishes, it is possible to change response code or response expectation to see how the test behaves for a trial run. I'll leave that to you to play around with.
The next sensible iteration follows where it may be obvious to separate the test from the actual source and as such, we'll take the test code include the main file such that our structure simply looks like:
prj/
api.py
test.py
Due to the restructure, the app, obviously needs to imported from api.py so that test.py is aware of it. Simply the import is prepended to our test.py and the file becomes:
from api import app
from fastapi.testclient import TestClient
cl = TestClient(app)
def test_get():
rs = cl.get('/')
assert(rs.status_code == 200)
assert(rs.json() == {'msg': 'Hello, World.'})
if __name__ == '__main__':
test_get()
The test can then be executed as;
>python test.py
Of course, this is quite a naive example and in my experience those are the best type of examples to wet one's beak. Indeed, a better structuring of the API and subsequently the tests should be implemented for production cases that are likely to be much bigger in size. The plan is to follow up with some articles pertaining to that.
The code for this article is shared HERE!
Hope this helps!