DEV Community

Cover image for Why You Should Use a Single FastAPI App and TestClient Instance
Kfir
Kfir

Posted on

Why You Should Use a Single FastAPI App and TestClient Instance

When working with FastAPI, especially in larger projects, using one instance of your FastAPI app and one instance of TestClient across your entire project is critical for ensuring consistency, performance, and reliability. Let’s dive into why this is important and explore hands-on examples.


1. Consistency Across the Application

Creating multiple instances of your FastAPI app can lead to inconsistencies. Each app instance has its own state, middleware, and dependency management. If you share stateful data, like in-memory storage or database connections, having multiple instances can cause unexpected behavior.

2. Improved Performance

Each TestClient creates its own HTTP connection and initializes dependencies. Using one TestClient reduces overhead and makes tests faster.

3. Avoid Initialization Issues

FastAPI apps often initialize resources like database connections or background tasks during startup. Multiple instances can result in duplicate initializations or conflicts.


Hands-On Code Example

Correct: One App Instance, One TestClient

from fastapi import FastAPI, Depends
from fastapi.testclient import TestClient

# Create a single FastAPI app instance
app = FastAPI()

# Simple in-memory database
database = {"items": []}

# Dependency
def get_database():
    return database

@app.post("/items/")
def create_item(item: str, db=Depends(get_database)):
    db["items"].append(item)
    return {"message": f"Item '{item}' added."}

@app.get("/items/")
def list_items(db=Depends(get_database)):
    return {"items": db["items"]}

# Create a single TestClient instance
client = TestClient(app)

# Tests
def test_create_item():
    response = client.post("/items/", json={"item": "foo"})
    assert response.status_code == 200
    assert response.json() == {"message": "Item 'foo' added."}

def test_list_items():
    response = client.get("/items/")
    assert response.status_code == 200
    assert response.json() == {"items": ["foo"]}
Enter fullscreen mode Exit fullscreen mode

Incorrect: Multiple Instances Can Cause Issues

# Incorrect: Multiple app instances
app1 = FastAPI()
app2 = FastAPI()

# Incorrect: Multiple TestClient instances
client1 = TestClient(app1)
client2 = TestClient(app2)

# Issue: State changes in client1 won't reflect in client2
Enter fullscreen mode Exit fullscreen mode

Common Issues with Multiple Instances

  1. State Inconsistency:

    • Shared state (like database) will behave independently in different app instances.
  2. Multiple Dependency Initializations:

    • Dependencies like database connections may be initialized multiple times, leading to resource exhaustion.
  3. Startup/Shutdown Event Overlap:

    • Multiple app instances trigger these events independently, causing redundant or conflicting behavior.

Best Practices

Structure Your Project for Reuse

Create your app in a dedicated file (e.g., app.py) and import it where needed.

# app.py
from fastapi import FastAPI

app = FastAPI()
# Add routes here
Enter fullscreen mode Exit fullscreen mode
# main.py
from fastapi.testclient import TestClient
from app import app

client = TestClient(app)
Enter fullscreen mode Exit fullscreen mode

Use pytest Fixtures for Shared Instances

Fixtures in pytest help manage shared resources like the TestClient:

import pytest
from fastapi.testclient import TestClient
from app import app

@pytest.fixture(scope="module")
def test_client():
    client = TestClient(app)
    yield client  # Ensure cleanup
Enter fullscreen mode Exit fullscreen mode
def test_example(test_client):
    response = test_client.get("/items/")
    assert response.status_code == 200
Enter fullscreen mode Exit fullscreen mode

Relevant Documentation

By following these guidelines, you ensure your FastAPI project is consistent, efficient, and maintainable.


Photo by Shawon Dutta: https://www.pexels.com/photo/beach-bluesky-beautiful-sky-blue-sea-7759874/

Top comments (0)