# How to Test FastCRUD Endpoints: A Complete Guide with Examples

> Easily test FastCRUD endpoints using FastAPI TestClient and async session injection. Follow our complete guide with examples to verify your auto-generated CRUD routes efficiently.

- Repository: [Benav Labs/fastcrud](https://github.com/benavlabs/fastcrud)
- Tags: how-to-guide
- Published: 2026-02-26

---

**Test FastCRUD endpoints by creating a temporary FastAPI application with the generated `crud_router`, injecting an async test session dependency, and using FastAPI's `TestClient` to execute HTTP requests against the auto-generated CRUD routes.**

FastCRUD accelerates API development by automatically generating REST endpoints from SQLModel or SQLAlchemy models. Because the router is constructed dynamically at runtime in [`fastcrud/endpoint/crud_router.py`](https://github.com/benavlabs/fastcrud/blob/main/fastcrud/endpoint/crud_router.py), testing requires bootstrapping a full FastAPI application within your test suite. This guide demonstrates the exact patterns used in the **benavlabs/fastcrud** repository to validate endpoint behavior, including dependency injection, soft deletes, and advanced query parameters.

## Understanding FastCRUD's Endpoint Architecture

Before writing tests, you must understand how FastCRUD constructs its API layer. The library generates routes through two primary components:

- **[`fastcrud/endpoint/crud_router.py`](https://github.com/benavlabs/fastcrud/blob/main/fastcrud/endpoint/crud_router.py)** – The factory function that builds an `APIRouter` instance based on your model, Pydantic schemas, and configuration options.
- **[`fastcrud/endpoint/endpoint_creator.py`](https://github.com/benavlabs/fastcrud/blob/main/fastcrud/endpoint/endpoint_creator.py)** – The `EndpointCreator` class that registers individual route handlers, handles request/response validation, and wires dependencies.

When you call `crud_router()`, FastCRUD inspects your model and creates standard REST endpoints (POST `/create`, GET `/get/{id}`, GET `/get_multi`, PATCH `/update/{id}`, DELETE `/delete/{id}`) with built-in support for pagination, filtering, and sorting.

## Setting Up the Test Environment

The repository provides a reference implementation for test infrastructure in [`tests/sqlmodel/conftest.py`](https://github.com/benavlabs/fastcrud/blob/main/tests/sqlmodel/conftest.py). Your test suite requires three core components:

1. **An async database engine** (typically SQLite with `aiosqlite` for in-memory testing)
2. **A session fixture** that yields `AsyncSession` instances
3. **A FastAPI `TestClient`** that wraps your temporary app

This pattern ensures each test runs against an isolated, fresh database schema while maintaining full compatibility with FastCRUD's async repository pattern.

## Bootstrapping a Temporary FastAPI Application

Because FastCRUD routers are generated at runtime, you cannot import them as static modules in tests. Instead, instantiate the router inside a fixture and mount it to a fresh `FastAPI` application.

```python
import pytest
from fastapi import FastAPI
from fastapi.testclient import TestClient
from sqlmodel import SQLModel
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine, async_sessionmaker
from typing import AsyncGenerator

# Import FastCRUD components

from fastcrud.endpoint.crud_router import crud_router
from your_app.models import Item
from your_app.schemas import CreateItemSchema, UpdateItemSchema

@pytest.fixture
async def async_session():
    """Create a new async session for each test."""
    engine = create_async_engine("sqlite+aiosqlite:///:memory:", echo=False)
    async with engine.begin() as conn:
        await conn.run_sync(SQLModel.metadata.create_all)
    
    session_factory = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
    async with session_factory() as session:
        yield session
    await engine.dispose()

@pytest.fixture
def client(async_session):
    """Build a FastAPI app with the CRUD router and return a TestClient."""
    
    async def lifespan(app: FastAPI):
        # Tables already created in session fixture

        yield
    
    app = FastAPI(lifespan=lifespan)
    
    # Dependency injection for the test session

    async def get_session() -> AsyncGenerator[AsyncSession, None]:
        yield async_session
    
    # Generate the CRUD router at runtime

    item_router = crud_router(
        session=get_session,
        model=Item,
        create_schema=CreateItemSchema,
        update_schema=UpdateItemSchema,
        path="/items",
        tags=["Items"]
    )
    
    app.include_router(item_router)
    return TestClient(app)

```

## Seeding Test Data

FastCRUD tests interact with the database through the same SQLModel/SQLAlchemy models used by the application. Insert test records directly using the async session before executing HTTP requests.

```python
async def seed_test_data(session: AsyncSession):
    """Helper to populate the database with sample records."""
    test_items = [
        Item(name="Laptop", price=999.99, is_deleted=False),
        Item(name="Mouse", price=25.50, is_deleted=False),
        Item(name="Keyboard", price=75.00, is_deleted=False)
    ]
    for item in test_items:
        session.add(item)
    await session.commit()

```

## Testing Core CRUD Operations

Verify that the generated endpoints correctly handle the full lifecycle of a resource. The default paths follow the convention: `/items/create`, `/items/get/{id}`, `/items/update/{id}`, and `/items/delete/{id}`.

```python
@pytest.mark.asyncio
async def test_create_read_update_delete_flow(client, async_session):
    """Test the complete CRUD cycle including soft delete."""
    
    # ---------- CREATE ----------

    create_payload = {"name": "Monitor", "price": 299.99}
    response = client.post("/items/create", json=create_payload)
    assert response.status_code == 201
    created_item = response.json()
    assert created_item["name"] == "Monitor"
    item_id = created_item["id"]
    
    # ---------- READ SINGLE ----------

    response = client.get(f"/items/get/{item_id}")
    assert response.status_code == 200
    fetched = response.json()
    assert fetched["price"] == 299.99
    
    # ---------- UPDATE ----------

    update_payload = {"price": 249.99}
    response = client.patch(f"/items/update/{item_id}", json=update_payload)
    assert response.status_code == 200
    updated = response.json()
    assert updated["price"] == 249.99
    
    # ---------- DELETE (Soft) ----------

    response = client.delete(f"/items/delete/{item_id}")
    assert response.status_code == 204
    
    # Verify soft delete (item should be excluded from GET)

    response = client.get(f"/items/get/{item_id}")
    assert response.status_code == 404

```

## Testing Pagination, Filtering, and Sorting

FastCRUD endpoints accept query parameters defined in the internal `PaginatedRequestQuery` and `FilterConfig` mechanisms. Test these by asserting on the `data` array returned in the paginated response structure.

```python
@pytest.mark.asyncio
async def test_get_multi_with_query_params(client, async_session):
    """Test pagination, filtering, and sorting via query strings."""
    await seed_test_data(async_session)
    
    # Test pagination (page=1, itemsPerPage=2)

    response = client.get("/items/get_multi?page=1&itemsPerPage=2")
    assert response.status_code == 200
    result = response.json()
    assert len(result["data"]) == 2
    assert result["total"] == 3  # Total count before pagination

    
    # Test filtering by exact match

    response = client.get("/items/get_multi?name=Mouse")
    assert response.status_code == 200
    data = response.json()["data"]
    assert len(data) == 1
    assert data[0]["name"] == "Mouse"
    
    # Test filtering with operators (e.g., price greater than)

    response = client.get("/items/get_multi?price__gt=50")
    assert response.status_code == 200
    items = response.json()["data"]
    assert all(item["price"] > 50 for item in items)
    
    # Test sorting descending by price

    response = client.get("/items/get_multi?sort=-price")
    assert response.status_code == 200
    items = response.json()["data"]
    prices = [item["price"] for item in items]
    assert prices == sorted(prices, reverse=True)

```

## Testing Custom Dependencies

If your production router includes custom dependencies (such as authentication), override them in the test fixture using FastAPI's `app.dependency_overrides` dictionary.

```python
def override_auth():
    return {"user_id": "test-user", "role": "admin"}

@pytest.fixture
def client_with_auth(async_session):
    app = FastAPI()
    
    async def get_session():
        yield async_session
    
    router = crud_router(
        session=get_session,
        model=Item,
        create_schema=CreateItemSchema,
        dependencies=[Depends(some_auth_dependency)]  # Production dependency

    )
    
    app.include_router(router)
    app.dependency_overrides[some_auth_dependency] = override_auth
    
    return TestClient(app)

```

## Summary

- **FastCRUD generates endpoints dynamically** through `crud_router()` in [`fastcrud/endpoint/crud_router.py`](https://github.com/benavlabs/fastcrud/blob/main/fastcrud/endpoint/crud_router.py), requiring you to instantiate the router inside test fixtures rather than importing static routes.
- **Use an async SQLite engine** and `TestClient` as demonstrated in [`tests/sqlmodel/conftest.py`](https://github.com/benavlabs/fastcrud/blob/main/tests/sqlmodel/conftest.py) to provide isolated database sessions for each test.
- **Test the full HTTP contract** including status codes, JSON response shapes, and the `data` wrapper object used in paginated responses.
- **Validate advanced features** by passing query parameters (`page`, `itemsPerPage`, `sort`, `field__operator`) to `get_multi` endpoints and asserting on filtered results.
- **Override dependencies** in your test app to bypass authentication or other FastAPI dependencies while testing the underlying CRUD logic.

## Frequently Asked Questions

### How do I test endpoints that require authentication in FastCRUD?

Override the authentication dependency in your test fixture using `app.dependency_overrides`. Map your production dependency callable to a mock function that returns a test user object or valid token. This allows you to test the CRUD logic without configuring a live identity provider.

### Can I use a synchronous database session to test FastCRUD endpoints?

FastCRUD is built on async SQLAlchemy patterns, and the `crud_router` function expects an async session dependency. While you could theoretically wrap synchronous calls, the repository's own test suite in `tests/sqlmodel/` uses `AsyncSession` exclusively with `create_async_engine` and `aiosqlite` to match the library's internal async repository implementation.

### How do I verify soft-delete behavior in my tests?

First, create a record via POST, then delete it via DELETE and assert a 204 status. Subsequently, attempt a GET request to `/get/{id}` and verify it returns 404. If you need to test recovery or audit trails, query the database directly through your test session to check that `is_deleted` is True or `deleted_at` is populated, depending on your model configuration.

### What is the best way to test complex filter combinations?

Construct URLs with multiple query parameters and assert on the returned dataset size and content. FastCRUD supports operators like `__gt`, `__lt`, `__startswith`, and `__contains`. Test each operator individually, then combine them (e.g., `?price__gt=10&price__lt=100&name__startswith=Pro`) to ensure the filtering logic in `fastcrud/core/` correctly applies AND conditions between parameters.