Unit Testing FastAPI with a Database: Choosing Between Mock and Local Database

Posted by


When building a web application with a FastAPI backend, it’s crucial to ensure that your code is thoroughly tested to catch bugs and errors before deploying them to production. One area that requires careful testing is interactions with the database.

In this tutorial, we will discuss the correct approach to unit testing FastAPI endpoints that interact with a database, and we will compare two common strategies: using mock objects and using a local database for testing.

Setup and Installation

Before we dive into unit testing, make sure you have FastAPI and your preferred database library installed. For this tutorial, we will be using SQLAlchemy as our ORM and SQLite as the database. You can install everything using pip:

pip install fastapi
pip install sqlalchemy
pip install databases

The Correct Approach to Unit Testing

When testing FastAPI endpoints that interact with a database, it’s essential to test the interactions between your API and the database without relying on an actual production database. This is because testing with a real database can be slow, unreliable, and may result in unexpected side effects on your production data.

Instead, we should isolate our tests from the database by mocking the database interactions or using a separate local database specifically for testing purposes. Let’s take a look at both approaches and see how they compare.

Mocking Database Interactions

Mocking involves replacing the actual database interactions with mock objects that mimic the expected behavior of the database. This approach is ideal for unit testing, as it allows you to focus on testing the interactions between your API endpoints and the database without actually touching the database.

To mock database interactions in FastAPI tests, you can use libraries like unittest.mock or pytest-mock. Here’s an example of how you can mock a database session in a FastAPI test using pytest:

from fastapi.testclient import TestClient
from unittest.mock import MagicMock
from app.main import app

def test_create_user():
    client = TestClient(app)
    mock_db_session = MagicMock()
    app.dependency_overrides[get_db] = lambda: mock_db_session

    response = client.post("/users/", json={"name": "Alice"})
    assert response.status_code == 201
    assert mock_db_session.add.called
    assert mock_db_session.commit.called

In this example, we are creating a mock database session using MagicMock and overriding the dependency to return the mock session. This way, when the create_user endpoint is called, the database session is mocked and we can check if the correct interactions with the database are happening.

Using a Local Database for Testing

Another approach to unit testing FastAPI endpoints with database interactions is to use a separate local database specifically for testing. This involves setting up a test database that mirrors your production database schema and running your tests against it.

To set up a local test database, you can create a SQLite in-memory database or use a temporary file-based SQLite database for testing. Here’s an example of how you can set up a test database and run your FastAPI tests against it using pytest:

import pytest
from fastapi.testclient import TestClient
from databases import Database
from app.main import app

@pytest.fixture
def test_db():
    db = Database('sqlite:///test.db')
    return db

def test_create_user(test_db):
    client = TestClient(app)
    response = client.post("/users/", json={"name": "Alice"})
    assert response.status_code == 201

In this example, we are setting up a SQLite database called test.db and using it to run our tests against. By using a local database for testing, we can ensure that our tests are isolated and won’t have any impact on our production database.

Conclusion

In conclusion, when unit testing FastAPI endpoints with database interactions, it’s important to isolate the tests from the actual production database. Mocking database interactions using mock objects or using a local database specifically for testing are two common approaches to achieve this.

While mocking can be useful for simple tests, it may not be sufficient for more complex scenarios. Using a local database for testing provides a more realistic environment for testing without affecting the production database.

Ultimately, the choice between mocking and using a local database for testing depends on the complexity of your application and your testing requirements. Whichever approach you choose, make sure to write thorough and comprehensive tests to ensure the reliability and stability of your FastAPI backend.

0 0 votes
Article Rating
4 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
@reevesnmortimer
29 days ago

Great video & completely agree with your approach. I go a step further a separate the responsibilities of 1. Handling the routing to business logic and 2. Database insertion separately. I can then unit test both separately.

For 1., I use a mock db like you but for 2., I use a test db. I also have an integration test too. Do you think that is excessive?

@isaacomolewa9843
29 days ago

Nice explanation!
Somehow there is still a connection to the database. The example still add the entry to the database. This seems to contradict the intention in my own opinion

@MithunSellowpay
29 days ago

TypeError: object MagicMock can't be used in 'await' expression

@kevinrivas2608
29 days ago

The code does not work for me it says: AssertionError: Expected 'add' to have been called.