Skip to content
Descoped Source Code Portalv2.2.13
PollingSign in

fastapi-testing

Up to date

FastAPI Testing

descoped/fastapi-testing on GitHubLast push

last commit badgerelease badgelicense badgeissues badge

Build Status Coverage Python Version Release

A lightweight, async-first testing framework designed specifically for FastAPI applications. This library provides a simple way to write integration tests for FastAPI applications with proper lifecycle management and async support.

Features

  • Async-first design for modern Python applications
  • Automatic port management for test servers
  • Clean lifecycle management with context managers
  • Built-in HTTP and WebSocket client support
  • Proper cleanup of resources after tests
  • Support for FastAPI's lifespan events
  • Type-safe with full typing support

Installation

uv add fastapi-testing

Or with pip:

pip install fastapi-testing

Quick Start

Here's a simple example of how to test a FastAPI endpoint:

import pytest
from fastapi import FastAPI
from fastapi_testing import create_test_server

@pytest.mark.asyncio
async def test_hello_world():
    async with create_test_server() as server:
        @server.app.get("/hello")
        async def hello():
            return {"message": "Hello, World!"}
            
        response = await server.client.get("/hello")
        await response.expect_status(200)
        data = await response.json()
        assert data["message"] == "Hello, World!"

Architecture

The following sequence diagram illustrates the lifecycle of a test using this framework:

sequenceDiagram
    participant Test
    participant AsyncTestServer
    participant PortGenerator
    participant UvicornServer
    participant FastAPI
    participant AsyncTestClient

    Test->>+AsyncTestServer: create_test_server()
    AsyncTestServer->>+PortGenerator: get_port()
    PortGenerator-->>-AsyncTestServer: available port
    
    AsyncTestServer->>+UvicornServer: initialize
    UvicornServer->>FastAPI: configure
    
    AsyncTestServer->>+UvicornServer: start()
    UvicornServer->>FastAPI: startup event
    UvicornServer-->>AsyncTestServer: server ready
    
    AsyncTestServer->>+AsyncTestClient: initialize
    AsyncTestClient-->>-AsyncTestServer: client ready
    AsyncTestServer-->>-Test: server instance
    
    Note over Test,AsyncTestClient: Test execution happens here
    
    Test->>+AsyncTestServer: cleanup (context exit)
    AsyncTestServer->>+AsyncTestClient: close()
    AsyncTestClient-->>-AsyncTestServer: closed
    
    AsyncTestServer->>+UvicornServer: shutdown
    UvicornServer->>FastAPI: shutdown event
    UvicornServer-->>-AsyncTestServer: shutdown complete
    
    AsyncTestServer->>+PortGenerator: release_port()
    PortGenerator-->>-AsyncTestServer: port released
    AsyncTestServer-->>-Test: cleanup complete

Key Components

AsyncTestServer

The AsyncTestServer class is the core component that manages the lifecycle of your test FastAPI application:

from fastapi_testing import AsyncTestServer

server = AsyncTestServer()
await server.start()
# Use server.app to define routes
# Use server.client to make requests
await server.stop()

Context Manager

The recommended way to use the test server is with the async context manager:

from fastapi_testing import create_test_server

async with create_test_server() as server:
    # Your test code here
    pass  # Server automatically starts and stops

AsyncTestClient

The AsyncTestClient provides methods for making HTTP requests to your test server:

# Available HTTP methods
await server.client.get("/path")
await server.client.post("/path", json=data)
await server.client.put("/path", json=data)
await server.client.delete("/path")
await server.client.patch("/path", json=data)

Response Assertions

The AsyncTestResponse class provides convenient methods for assertions:

response = await server.client.get("/path")
await response.expect_status(200)  # Assert status code
data = await response.json()       # Get JSON response
text = await response.text()       # Get text response

WebSocket Testing

Test WebSocket endpoints with full protocol support:

@pytest.mark.asyncio
async def test_mixed_protocols(test_server):
    # Define HTTP endpoint
    @test_server.app.get("/api/data")
    async def get_data():
        return {"status": "ok"}

    # Define WebSocket endpoint
    @test_server.app.websocket("/ws/echo")
    async def websocket_endpoint(websocket: WebSocket):
        await websocket.accept(subprotocol="test-protocol")
        while True:
            try:
                message = await websocket.receive()
                if "text" in message:
                    data = json.loads(message["text"])
                    await websocket.send_json(data)
                elif "bytes" in message:
                    await websocket.send_bytes(message["bytes"])
            except WebSocketDisconnect:
                return

    # Test HTTP endpoint
    http_response = await test_server.client.get("/api/data")
    assert http_response.status_code == 200

    # Configure WebSocket
    config = WebSocketConfig(
        subprotocols=["test-protocol"],
        ping_interval=20.0,
        ping_timeout=20.0
    )

    # Test WebSocket endpoint
    ws_response = await test_server.client.websocket("/ws/echo", config)
    try:
        # Test JSON messages
        test_json = {"message": "test"}
        await test_server.client.ws.send_json(ws_response, test_json)
        response = await test_server.client.ws.receive_json(ws_response)
        assert response == test_json

        # Test binary messages
        test_data = b"binary test"
        await test_server.client.ws.send_binary(ws_response, test_data)
        response = await test_server.client.ws.receive_binary(ws_response)
        assert response == test_data
    finally:
        await ws_response.websocket().close()

WebSocket Message Operations

The WebSocketHelper provides comprehensive message handling:

# Send Operations
await client.ws.send_text(ws_response, "message")
await client.ws.send_json(ws_response, {"key": "value"})
await client.ws.send_binary(ws_response, b"data")

# Receive Operations
text = await client.ws.receive_text(ws_response)
json_data = await client.ws.receive_json(ws_response)
binary = await client.ws.receive_binary(ws_response)

# Message Expectations
await client.ws.expect_message(
    ws_response,
    expected="message",
    timeout=1.0
)

# Collect Multiple Messages
messages = await client.ws.drain_messages(
    ws_response,
    timeout=1.0
)

WebSocket Configuration

Configure connections with various options:

ws_config = WebSocketConfig(
    subprotocols=["protocol"],  # Supported subprotocols
    compression=None,           # Compression algorithm
    extra_headers={},          # Additional headers
    ping_interval=20.0,        # Keep-alive interval
    ping_timeout=20.0,         # Ping timeout
    max_size=2 ** 20,         # Max message size (1MB)
    max_queue=32,             # Max queued messages
    timeout=30.0              # Connection timeout
)

Advanced Usage

Advanced Server Configuration

You can customize the server lifecycle using a reusable test fixture:

@pytest.fixture
async def test_server(
    test_settings: Settings,
    transaction_manager: TransactionManager
) -> AsyncGenerator[AsyncTestServer, None]:
    """Create test server with overridden settings and database connection"""

    async def custom_lifespan(app: AppType) -> AsyncGenerator[None, Any]:
        # Wire up test-specific dependencies
        app.dependency_overrides.update({
            get_settings: lambda: test_settings,
            get_transaction_manager: lambda: transaction_manager,
            get_db_pool: lambda: transaction_manager.pool
        })

        yield  # Server handles requests during this period

        # Cleanup after tests complete
        await db.cleanup()

    async with create_test_server(lifespan=custom_lifespan) as server:
        yield server

Testing Routes and Routers

You can test entire routers and complex route configurations:

@pytest.mark.asyncio
async def test_api(test_server: AsyncTestServer):
    # Register routes/routers
    test_server.app.include_router(your_router)

    # Make requests
    response = await test_server.client.get("/your-endpoint")
    await response.expect_status(200)
    
    # Test concurrent requests
    responses = await asyncio.gather(*[
        test_server.client.get("/endpoint")
        for _ in range(5)
    ])
    
    for response in responses:
        await response.expect_status(200)

Lifecycle Management

You can define setup and cleanup operations using FastAPI's lifespan:

from contextlib import asynccontextmanager
from fastapi import FastAPI

@asynccontextmanager
async def lifespan(app: FastAPI):
    # Setup
    print("Starting server")
    yield
    # Cleanup
    print("Shutting down server")

async with create_test_server(lifespan=lifespan) as server:
    # Your test code here
    pass

Concurrent Requests

The framework supports testing concurrent requests:

import asyncio

async with create_test_server() as server:
    @server.app.get("/ping")
    async def ping():
        return {"status": "ok"}

    responses = await asyncio.gather(*[
        server.client.get("/ping")
        for _ in range(5)
    ])

Configuration

You can customize the server behavior:

server = AsyncTestServer(
    startup_timeout=30.0,    # Seconds to wait for server startup
    shutdown_timeout=10.0,   # Seconds to wait for server shutdown
)

Best Practices

  1. Always use the async context manager (create_test_server) when possible
  2. Clean up resources in your tests, especially when managing state
  3. Use pytest.mark.asyncio for your test functions
  4. Handle exceptions appropriately in your tests
  5. Use type hints to catch potential issues early

Testing

The framework itself is thoroughly tested with >88% code coverage, ensuring reliability for production use.

Running Tests

# Run all tests
uv run pytest

# Run with coverage report
uv run pytest --cov=src/fastapi_testing --cov-report=term-missing

# Run specific test categories
uv run pytest tests/test_server.py          # Server functionality
uv run pytest tests/test_websockets.py     # WebSocket support
uv run pytest tests/test_config.py         # Configuration tests

Test Structure

The test suite includes:

  • Integration tests - Real FastAPI applications with actual network connections
  • Configuration tests - Environment variable loading and validation
  • Error handling tests - Edge cases and failure scenarios
  • WebSocket tests - Real-time bidirectional communication
  • Lifecycle tests - Server startup, shutdown, and resource management
  • Concurrent request tests - Multiple simultaneous connections

All tests follow modern async/await patterns and avoid mocks to ensure real-world reliability.

Error Handling

The framework provides clear error messages for common issues:

  • Server startup timeout
  • Port allocation failures
  • Connection errors
  • Invalid request formats

Limitations

  • Only supports async test cases
  • Requires Python 3.11+
  • Designed specifically for FastAPI applications

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Dependencies

pyproject.toml fastapi-testing (12 dependencies)
NameVersionScope
fastapi>=0.137.2runtime
httpx>=0.28.1runtime
starlette>=1.3.1runtime
uvicorn>=0.49.0runtime
websockets>=16.0runtime
pydantic>=2.13.4dev
pygments>=2.20.0dev
pytest>=9.1.0dev
pytest-asyncio>=1.4.0dev
pytest-cov>=7.1.0dev
restructuredtext-lint>=2.0.2dev
ruff>=0.15.17dev

Health

Health 75Watch

Technology

Up to date
Languages
Python
Build tools
uv
Toolchains
python >=3.11
Used by

Packages

  • fastapi-testingPyPI0.3.3at pypi.org

Pull requests

Up to date

5 open: Needs review 5

  1. chore(deps): update anyio to 4.14.2 [SECURITY]

    Needs review, waits for a review#21renovate[bot] (automation)

  2. chore(deps): update h2 to 4.4.1 [SECURITY]

    Needs review, waits for a review#23renovate[bot] (automation)

  3. chore(deps): update certifi to 2026.10.18 [SECURITY]

    Needs review, waits for a review#22renovate[bot] (automation)

  4. chore(deps): update hpack to 4.2.0 [SECURITY]

    Needs review, waits for a review#24renovate[bot] (automation)

  5. chore(deps): Lock file maintenance

    Needs review, waits for a review#11renovate[bot] (automation)

Issues

Up to date

1 open: Needs triage 1

  1. Dependency Dashboard

    Needs triage, waits for a maintainer to look at it#1renovate[bot] (automation)idle

Releases

Up to date
  1. v0.3.3Release v0.3.3 by oranheim
    Release notes

    Maintenance & Dependency Release

    The library API is unchanged from v0.3.2 — src/fastapi_testing is byte-identical. This release modernizes dependencies (security fixes + latest compatible versions) and CI tooling.

    Dependencies

    • Security updates: starlette, pygments, and pytest moved to patched releases.
    • Minimums raised to the latest releases that still support Python ≥ 3.11:
      • Runtime: fastapi>=0.137.2, starlette>=1.3.1, uvicorn>=0.49.0, websockets>=16.0
      • Dev/build: pytest>=9.1.0, pytest-asyncio>=1.4.0, pytest-cov>=7.1.0, pydantic>=2.13.4, ruff>=0.15.17, hatchling>=1.30.1

    Note for consumers: installing fastapi-testing 0.3.3 now pulls in the newer FastAPI / Starlette / Uvicorn minimums listed above.

    CI / Tooling

    • GitHub Actions upgraded to current majors: checkout v7, setup-python v6, setup-uv v8.2.0, codecov-action v7, attest-build-provenance v4.
    • Fixed the pinned uv version in CI (0.5.11 → 0.11.13) so it can parse the current revision = 3 lockfile.
    • "Build and Test" now runs on pull requests.

    Compatibility

    • Python 3.11–3.13. No public API changes; no migration required.

    Full Changelog: https://github.com/descoped/fastapi-testing/compare/v0.3.2...v0.3.3

  2. v0.3.2Release v0.3.2 by oranheim
    Release notes

    New Features

    Public Headers API for AsyncTestResponse

    The AsyncTestResponse class now provides a public headers property, eliminating the need to access the protected _response member for header retrieval.

    Before

    # Previously required accessing protected member
    location = response._response.headers.get("location")
    

    After

    # Clean public API
    location = response.headers.get("location")
    

    Improvements

    • Better API Design: Headers are now accessible through a clean, public property interface
    • Consistent Error Handling: Attempting to access headers on WebSocket connections raises InvalidResponseTypeError with a clear message
    • Case-Insensitive Access: Headers can be accessed case-insensitively through httpx.Headers interface

    Testing

    • Added comprehensive tests for HTTP response header access
    • Added tests for redirect responses with Location header
    • Added WebSocket error handling tests to ensure proper exceptions are raised

    Example Usage

    async with create_test_server() as server:
        @server.app.post("/login")
        async def login_endpoint():
            return Response(
                status_code=302,
                headers={"Location": "/dashboard"}
            )
        
        response = await server.client.post("/login", follow_redirects=False)
        
        # Access headers directly
        assert response.status_code == 302
        assert response.headers["Location"] == "/dashboard"
        
        # Case-insensitive access
        location = response.headers.get("location")  # Works!
    

    Breaking Changes

    None - this is a backwards-compatible enhancement.


    Full Changelog: https://github.com/descoped/fastapi-testing/compare/v0.3.1...v0.3.2

  3. v0.3.1Release v0.3.1 by oranheim
    Release notes

    🚀 FastAPI Testing v0.3.1

    Code Quality & Testing Enhancement Release

    This release significantly improves code quality, testing coverage, and development experience while maintaining full backward compatibility.

    ✨ What's New

    • Modern Code Formatting: Integrated [Ruff](https://github.com/astral-sh/ruff) as the primary code formatter and linter
    • Comprehensive Test Coverage: Added extensive edge case and real-world scenario tests
    • Enhanced CI/CD: Improved workflow reliability with better test coverage reporting
    • Modern Python Syntax: Updated to Python 3.11+ type annotations throughout the codebase

    🧪 Testing Improvements

    • Edge Case Coverage: New tests for port exhaustion, invalid ranges, and connection failures
    • WebSocket Testing: Enhanced WebSocket tests with type error handling and timeout scenarios
    • Server Lifecycle: Comprehensive tests for server startup, shutdown, and error conditions
    • Configuration Testing: Full coverage of environment variable handling and validation
    • Concurrent Operations: Tests for multiple simultaneous connections and requests

    🔧 Code Quality Enhancements

    • Type Annotations: Modernized to use list[T] instead of List[T] syntax
    • Error Handling: Replaced try/except blocks with contextlib.suppress where appropriate
    • Import Organization: Consistent import sorting and organization across all files
    • Code Formatting: Standardized formatting with Ruff configuration
    • Documentation: Updated with project badges and improved README

    📦 Development Tools

    • Ruff Integration: Comprehensive linter and formatter configuration
    • CI Improvements: Enhanced GitHub Actions workflows with better test reporting
    • Code Coverage: Improved coverage reporting with Codecov integration

    🔄 Migration Notes

    This release is fully backward compatible. No changes are required for existing users. The API remains unchanged while the internal code quality has been significantly improved.

    🎯 Benefits for Contributors

    • Faster Development: Automated code formatting with Ruff
    • Better Testing: Comprehensive test suite covering edge cases
    • Consistent Style: Enforced code style across the entire codebase
    • Modern Practices: Updated to contemporary Python development standards
  4. v0.3.0Release v0.3.0 by oranheim
    Release notes

    🚀 FastAPI Testing v0.3.0

    Major Tooling Modernization Release

    This release brings significant improvements to the development workflow and build system while maintaining full backward compatibility.

    ✨ What's New

    • Modern Package Management: Migrated from Poetry to [uv](https://github.com/astral-sh/uv) for faster dependency resolution and package management
    • Automated Releases: Added GitHub Actions workflow for automated releases and publishing
    • Enhanced CI/CD: New build and test workflows with coverage reporting via Codecov
    • Dependency Automation: Configured Renovate bot for automated dependency updates

    🔧 Technical Changes

    • Build System: Switched from Poetry to hatchling with PEP 621 compliant pyproject.toml
    • Lock Files: Replaced poetry.lock with uv.lock for more efficient dependency resolution
    • Testing: Added pytest-cov for comprehensive test coverage reporting
    • Project Structure: Fixed source folder structure for better package distribution
  5. v0.2.2Release v0.2.2 by oranheim
    Release notes

    Configuration simplification:

    • Removed dependencies: pydantic-settings and python-dotenv
    • Replaced with lightweight Config class with explicit env var loading
    • Moved pydantic to dev dependencies only
    • Improved VS Code configuration settings
  6. v0.2.1Release v0.2.1 by oranheim
    Release notes

    (feat) add full WebSocket support and modernize codebase.

    Add comprehensive WebSocket functionality with protocol-level support and connection management.

    Key changes:

    • Add WebSocket testing capabilities with robust message handling
    • Implement WebSocketConfig and WebSocketHelper classes
    • Add retry logic and proper cleanup for WebSocket connections
    • Add environment-based configuration via pydantic-settings
  7. v0.2.0Release v0.2.0 by oranheim
    Release notes

    (refactor) rename Test-prefixed classes to Async prefix

    Rename classes prefixed with "Test" to "Async" to prevent pytest from treating them as test classes. This fixes pytest collection warnings.

    • Rename TestServer to AsyncTestServer
  8. v0.1.1Release v0.1.1 by oranheim
    Release notes

    fix: Downgrade httpx dependency for better compatibility

    • Constrain httpx version to >=0.23.0,<0.28.0 for stability
    • Update documentation link to point to master branch
    • Remove pytest.ini in favor of pyproject.toml configuration

    Breaking changes: None

  9. v0.1.0Initial Release v0.1.0 by oranheim
    Release notes

    Initial release of FastAPI Testing framework with async-first design, automatic port management, and clean lifecycle management.

    Includes TestServer, TestClient, and TestResponse components with full FastAPI lifespan support.

    Requires Python 3.11+, FastAPI 0.115.0+

    Key features:

    • Async-first test framework
    • Automatic port management
    • Context manager support
    • Built-in async HTTP client
    • Resource cleanup
    • Type-safe design
All 9 held

Latest commits

Commit log

Documentation

No documents under docs/.