Testing FastAPI with pytest and MongoDB (Motor)
Testing your FastAPI applications is crucial for ensuring reliability and stability. When your application relies on MongoDB, you need to consider how to effectively test interactions with the database. This article guides you through the process of using pytest to test your FastAPI application integrated with MongoDB (using Motor).
Scenario: Testing a User CRUD API
Let's imagine you have a simple FastAPI application managing user data stored in a MongoDB database. Here's an example endpoint:
from fastapi import FastAPI, HTTPException
from fastapi.responses import JSONResponse
from typing import Optional
from pydantic import BaseModel
from motor.motor_asyncio import AsyncIOMotorClient
app = FastAPI()
client = AsyncIOMotorClient("mongodb://localhost:27017")
db = client["your_database_name"]
users_collection = db["users"]
class User(BaseModel):
id: Optional[str] = None
name: str
email: str
@app.post("/users/", response_model=User)
async def create_user(user: User):
user_dict = user.dict()
result = await users_collection.insert_one(user_dict)
user_dict["id"] = str(result.inserted_id)
return user_dict
@app.get("/users/{user_id}", response_model=User)
async def get_user(user_id: str):
user = await users_collection.find_one({"_id": user_id})
if user:
return User(**user)
raise HTTPException(status_code=404, detail="User not found")
Implementing pytest for Testing
Now, let's use pytest to test this code. The key is to set up a test database and client for your tests and properly handle database interactions within test functions.
import pytest
from fastapi.testclient import TestClient
from motor.motor_asyncio import AsyncIOMotorClient
from your_app import app
@pytest.fixture(scope="session")
async def test_client():
client = AsyncIOMotorClient("mongodb://localhost:27017")
db = client["your_test_database_name"]
yield TestClient(app)
await client.drop_database("your_test_database_name")
@pytest.fixture(scope="function", autouse=True)
async def clean_database(test_client):
"""Clears the test database before each test."""
db = test_client.app.client["your_test_database_name"]
await db["users"].delete_many({})
def test_create_user(test_client):
user_data = {"name": "Test User", "email": "[email protected]"}
response = test_client.post("/users/", json=user_data)
assert response.status_code == 200
assert response.json()["name"] == "Test User"
assert response.json()["email"] == "[email protected]"
def test_get_user(test_client):
user_data = {"name": "Test User", "email": "[email protected]"}
response = test_client.post("/users/", json=user_data)
user_id = response.json()["id"]
response = test_client.get(f"/users/{user_id}")
assert response.status_code == 200
assert response.json()["name"] == "Test User"
assert response.json()["email"] == "[email protected]"
def test_get_nonexistent_user(test_client):
response = test_client.get("/users/nonexistent_id")
assert response.status_code == 404
Explanation:
test_client
fixture: This fixture creates aTestClient
instance for your application and sets up a separate test database. It also ensures the test database is dropped after the test session.clean_database
fixture: This fixture runs before each test function and clears the test database, ensuring a clean slate for each test.- Test functions: Each test function tests specific functionality, using
test_client
to make requests and asserting expected outcomes.
Considerations and Best Practices:
- Test Database: Use a dedicated test database to avoid interfering with your production data.
- Database Isolation: Use fixtures to ensure a clean test environment before each test.
- Mocking/Patching: For complex or time-consuming database operations, consider using mocking or patching to isolate tests and improve performance.
- Test Data: Create realistic test data to comprehensively evaluate your application's behavior.
- Error Handling: Write tests to cover different error scenarios and ensure your API gracefully handles them.
Conclusion:
Testing your FastAPI application with MongoDB is essential for quality assurance. By using pytest and implementing effective testing strategies, you can ensure that your application functions as expected and remains robust even as it evolves. Remember to consider the best practices outlined above for creating comprehensive and effective tests.
Resources: