How to implement pytest for FastAPI with MongoDB(Motor)

3 min read 05-10-2024
How to implement pytest for FastAPI with MongoDB(Motor)


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 a TestClient 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: