If we do not want to keep starting our API and manually click through the endpoints to see if everything still works, we need to put a little effort into testing. Thanks to the underlying Starlette toolkit, we can test our FastAPI application in-memory and do not need a dedicated server. Let us explore how that helps us with testing our API.
This post is part of my journey to learn Python. You find the code for this post in my PythonFriday repository on GitHub.
Install pytest
I wrote about pytest a long time ago, yet most of it is still relevant and we can reuse our knowledge to test our FastAPI application. Before we start writing tests, we should install pytest and the coverage tools with the -U option. That will make sure that we use the current version of those packages and not an old one that we installed in the past:
1 2 3 |
pip install -U pytest pip install -U coverage pip install -U pytest-cov |
Turn the API into a package
Before we can write our tests, we need to make a small adjustment to our code. We need to create an empty file named __init__.py and put it next to our main.py file:
On Windows:
1 |
type nul > __init__.py |
On Linux:
1 |
touch __init__.py |
This file tells Python that our main.py file is part of a package and that allows us to import it into our test file using relative imports without modifying the path variable.
Create our first test
We need to import the TestClient and our application so that we can define a client for our API. If that is in place, we can use the client to call our API endpoint and check if the status code and the response match our expectation:
1 2 3 4 5 6 7 8 9 10 |
from fastapi.testclient import TestClient from .main import app client = TestClient(app) def test_read_root(): response = client.get("/") assert response.status_code == 200 assert response.json() == {"Hello": "World"} |
As you can see, we do not specify an IP address or a domain name for our API. Instead, we call the endpoints directly with their relative route. All the magic that we need to access the API and get the response back is handled by Starlette.
Add more tests
We can add more tests and check if the /items endpoint gives us the expected results:
1 2 3 4 5 6 7 8 9 10 |
def test_read_item(): response = client.get("/items/5") assert response.status_code == 200 assert response.json() == {'item_id': 5, 'q': None} def test_read_item_with_querry(): response = client.get("/items/10?q=abjdljlajlf") assert response.status_code == 200 assert response.json() == {'item_id': 10, 'q': 'abjdljlajlf'} |
To test query parameters or submit data as part of the route we can copy the URL out of the browser or from the Swagger UI and put it into our test client.
Test the error handling
While the happy path gets usually covered, the exception handling is often not tested. We can provoke an error by submitting the wrong data to our endpoint and check if it gives us back the expected error message:
1 2 3 4 5 |
def test_read_item_wrongly_called(): response = client.get("/items/rrr") assert response.status_code == 422 answer = response.json()['detail'] assert answer[0]['msg'] == 'Input should be a valid integer, unable to parse string as an integer' |
Create a coverage report
To check that we tested all the endpoints we can create a coverage report with this command:
1 |
pytest --cov-report html --cov |
This creates an HTML report that we can find inside the htmlcov folder:
Test the functions in isolation
So far, we explored our options to test the FastAPI endpoints. A more realistic application will have lots of code that is not coupled to FastAPI. We can test those functions in isolation as we do it with any other code. There is no need to test all the edge cases through the API endpoints when we can test the behaviour at a lower level.
Next
We created our first tests with the test client. When we now change something at our first two endpoints, we will get a notification from our tests. They may not look like much, but we have now a base for our next steps with FastAPI.
Next week we look at path and query parameters and how we can use them with FastAPI.
2 thoughts on “Python Friday #216: Test Your FastAPI Application”