Unit Testing with Pytest for FastAPI Applications: Synchronous and Asynchronous Endpoint Testing
Introduction:
Testing in the world of software development is crucial for the optimization of both your user's experience and development flow. Thinking about test cases to write these tests for, allow you to think a bit more about the user's request and the best ways to handle it. Test cases are written to test the interaction between the web application and the RESTful API. These tests ensure that data flows seamlessly between the client and the server. This is essential because, when building an application, ensuring users have the best experience is among your top priorities.
In this article, we would focus on how to test Fastapi applications using pytest, a popular python testing library. We would test both synchronous and asynchronous endpoints. To follow along, it would be nice to have a good grasp on how to write Restful APIs.
What are Tests?
Before a business is fully launched into the market a soft opening is done. This is a simulation of the actual opening which is usually done on a smaller scale. It is done to remove as many errors as possible before the main opening. This practice is what is called a test
Now, no matter how much we try to catch these errors, there are still going to be some that never occur during the tests. These errors would eventually pop up after the application has been pushed to the market and that is okay. The whole purpose of testing is not to catch every single error but rather, to catch as much errors as possible.
Why are tests important?
Finally!!! Jane has worked hard throughout the year to open up her restaurant. As long as she could remember, she always wanted to own a restaurant where her culinary skills could be displayed. The moment was here and Jane was putting the final touches. Although she was ecstatic, doubts soon began to creep in. What if it does not take off as planned? or people do not find the food good enough? A lot of what-ifs are coming up. She decides to put her restaurant to the test which would effectively clear her doubts. How about a soft opening, it would be like a real opening but on a smaller scale. This will be a test to make sure everything runs smoothly and she could make corrections where necessary before the actual opening. Now one of two things can happen during the soft opening:
Everything runs smoothly and she is good for the main opening
OR
Everything does not go as planned and she has to make some change
Seldom does the first instance ever happen because there are always changes to be made. Either ways she clears her doubt and becomes confident. In the case of the second possibility, she learns from the mistakes made, providing incredibly service to her clients.
In software development, it is among the best practices to test an application before it is released. Be it a server-side application or a client-side application putting it to the test is advised. In this article, we will cover how to use Python's test library; Pytest, to run unit tests on server side applications also known as backend applications built on the Fastapi framework before they are integrated into the client side. Writing tests for both synchronous and asynchronous endpoints.
What are Unit Tests
For the simulation, Jane decided that she would do a unit test. Instead of testing how the whole team in the restaurant worked together, that is, the flow between the bartenders, the waiter and the chefs, she was going to focus on how well the waiters worked separately from the chefs and the bartenders.
Unit tests are automated tests that check individual units or components of a software application to ensure they work as intended. These tests focus on small, isolated parts of the code, such as functions or methods, and are designed to catch errors early in the development process. Unit tests are quick to execute, repeatable, and help developers gain confidence in the reliability and correctness of their code.
What is Pytest?
Pytest is a popular Python testing framework used to test backend applications. It is popular due to its ease of use, flexibility, scalability, rich features and its large and active community. It can be used to write different types of tests for software applications including unit tests, integration tests, end-to-end tests, and functional tests. In this article, we would use pytest to write unit tests which it excels at.
Other popular Python testing frameworks are unittest and nose2
Synchronous Vs Asynchronous
The day for the soft opening is here and set to commence at noon. Jane looks around and everything is ready. For this event she had invited close friends, neighbors and her former colleagues. She spots her first costumers come in, welcomes them and takes their orders. Soon the restaurant is filled with people and buzzing with conversation. She looks around and notices how her waiters work. Waiter A take an order from one customer and brings it directly to the kitchen while Waiter B take about two to three orders at once. This makes Waiter A spend a lot of time completing orders compared to Waiter B. But Jane soon realizes that the customers Waiter A is attending to are still contemplating on what to order. This makes it efficient for Waiter A to deliver the orders one at a time. Although slower, it is necessary. While Waiter B is able to take up to 3 orders at once because the customers are ready.
Synchronous Programming:
In synchronous programming, tasks are executed one after another in a sequential order just like Waiter A. Each task must be completed before the program moves on to the next one. In synchronous programming, the program follows a clear and straightforward path. When a function or operation is initiated, the program waits for it to be completed before moving on to the next task. The sequence of actions is easy to predict and follows a linear order. This synchronous nature simplifies reasoning about the code but may lead to potential inefficiencies, especially when dealing with tasks that involve waiting, such as I/O operations or network requests.
Asynchronous Programming:
In contrast, asynchronous programming allows tasks to be executed independently without waiting for the completion of one before moving on to the next. Asynchronous operations are non-blocking, meaning that the program can continue to execute other tasks while waiting for certain operations to finish. This is particularly useful in scenarios where tasks involve waiting for external resources, like fetching data from a database, making network requests, or reading from a file. By leveraging asynchronous programming, developers can improve the overall efficiency and responsiveness of their applications.
How to Write Tests for Synchronous Endpoints
What are Synchronous Endpoints?
Synchronous endpoints in web development handle requests one at a time, where each request is processed sequentially, and the client waits for a response before proceeding. This approach is straightforward but can lead to delays, especially with tasks that involve waiting, like fetching data.
Testing a Synchronous Endpoint
In this example, we would create a synchronous endpoint for Sign up and write a test for it. Before writing this endpoint, we would consider the libraries to use, then install them.
Step 1- Setup your Environment:
To setup your work environment, you would install the frameworks we would be working with. To do this, we would install the request library; httpx, the test library; pytest, and python's backend framework; fastapi and it's server; uvicorn using python's package manager; pip.
pip install httpx pytest fastapi uvicorn
Step 2- Import Libraries:
After installation, import the libraries and classes needed and instantiate a FastAPI class.
Step 3- Create an Endpoint:
Write a schema for the endpoint you want to create.
In this case, it is a schema containing all the necessary fields for Sign Up.
Creating an Endpoint.
In this endpoint, the client provides a payload containing the username, password and email and the username and email is return.
Step 4- Fixture for Base URL:
- To write the test for the synchronous endpoint above, instantiate a pytest fixture and define a function that returns fastapi's test client. This test client creates a test environment which simulates the client.
Step 5- Test Function:
Define a synchronous test function (test_signUp) for testing.
Provide the data expected form the client (registration_data) and use the test client defined previously to simulate user registration and login.
Check that the status codes and data returned are what is expected.
Step 6- Running the Tests:
To run the tests, you'll need to run the pytest command. Here is how:
Place your code in a Python file, e.g., sync.py
Open a terminal and navigate to the directory where the file is located.
Run the Pytest test suite using this command:
pytest sync.py
Pytest will discover and run the test functions within the specified Python file.
What are Asynchronous Endpoints?
Asynchronous endpoints allow requests to be processed independently. The server can handle multiple requests simultaneously without waiting for each to complete, enhancing efficiency, particularly in scenarios with concurrent or time-consuming tasks. Asynchronous endpoints are beneficial for responsive and scalable applications, especially when dealing with operations that have varying durations, such as I/O operations or network requests.
How to Write Tests for Asynchronous Endpoints
Writing asynchronous tests is very similar to writing synchronous test. In this example, we would be using some code similar to the synchronous test
Here, we would create an asynchronous Sign up endpoint and write a test for it. Before writing this endpoint, we would again consider the libraries to use and install them.
Step 1- Set up your Environment:
To setup your work environment, you would install the frameworks we would be working with. To do this, you would install the request library; httpx, the test library; pytest, pytest-asyncio and python's backend framework; fastapi using the python installation package:
pip install httpx pytest fastapi uvicorn pytest-asyncio
Step 2- Import Libraries:
- Import the libraries and classes like we did before, but in this case we would import pytest_asyncio and AsyncIterator.
Step 3- Create an Endpoint:
Instantiate the FastAPI class.
-
Write a schema for the endpoint you want to create.
-
Creating an Endpoint.
Create your Sign Up endpoint just like we did before but in this case, we would include the async class. This is state that this is an asynchronous endpoint
Step 4- Fixture for Base URL:
- Define a Pytest fixture client that uses the httpx.AsyncClient to make asynchronous operations instead of the FastApi’s TestClient for making synchronous operations
Step 5- Test Function:
Define an asynchronous test function (test_signUp) for testing.
Use an async HTTP client (httpx.AsyncClient) to simulate user registration and login.
Assert the response status codes and verify that the data provided is accurate.
Step 6- Running the Tests:
To run the tests, you'll need run the pytest command. Here is how:
Place your in a Python file, e.g., async.py
Open a terminal and navigate to the directory where the file is located.
Run the Pytest test suite:
pytest async.py
Pytest will discover and run the test functions within the specified Python file.
Conclusion and Highlights
Synchronous vs. Asynchronous: The asynchronous endpoint uses asynchronous functions (
async def
) and fixtures (async def client()
) due to the use ofhttpx.AsyncClient
. This allows for asynchronous testing, which can be beneficial for testing asynchronous endpoints or functions.Test Client Type: The synchronous code uses
TestClient
fromfastapi.testclient
, while the asynchronous code useshttpx.AsyncClient
for asynchronous testing.Fixture Usage: In the synchronous code, the test client is a fixture without the
async
keyword (def client():
). In the asynchronous code, an asynchronous fixture is used with theasync
keyword (async def client():
).Test Function Decorator: The synchronous code uses the standard
@pytest.fixture()
decorator for the fixture and does not require the@pytest.mark.asyncio
decorator for the test function. In the asynchronous code, both the fixture and the test function use asynchronous decorators (@pytest_asyncio.fixture
and@pytest.mark.asyncio
).
Testing is like a soft opening for a restaurant, simulating real-world scenarios to catch errors early. We explored testing FastAPI applications with the pytest framework, covering both synchronous and asynchronous endpoints.
Synchronous endpoints process requests one at a time, like a waiter taking orders sequentially. Asynchronous endpoints handle requests independently, enhancing efficiency, similar to a waiter efficiently managing multiple orders simultaneously. We used Pytest to write unit tests, automated checks ensuring individual code units work correctly. Pytest's versatility and simplicity make it ideal for such tests.
Understanding synchronous and asynchronous programming lies in how tasks are executed and handle waiting times. Synchronous follows a clear, sequential path, while asynchronous allows independent task execution, improving efficiency. Testing helps catch errors early, build with confidence, and provide a seamless user experience.