SuperAPI AI Directory

fastmcp-python-tests

Coding & development

View repository

Updated 10-15 22:40

About this skill

Writing Effective Python Tests [1]

Core Principles

Every test should be atomic, self-contained, and test single functionality. A test that tests multiple things is harder to debug and maintain.

Test Structure

Atomic unit tests

Each test should verify a single behavior. The test name should tell you what's broken when it fails. Multiple assertions are fine when they all verify the same behavior.

# Good: Name tells you what's broken
def test_user_creation_sets_defaults():
    user = User(name="Alice")
    assert user.role == "member"
    assert user.id is not None
    assert user.created_at is not None
Bad: If this fails, what behavior is broken?

def test_user(): user = User(name="Alice") assert user.role == "member" user.promote() assert user.role == "admin" assert user.can_delete_others()


### Use parameterization for variations of the same concept

```python
import pytest


@pytest.mark.parametrize("input,expected", [("hello", "HELLO"), ("World", "WORLD"), ("", ""), ("123", "123")])
def test_uppercase_conversion(input, expected):
    assert input.upper() == expected

Use separate tests for different functionality

Don't parameterize unrelated behaviors. If the test logic differs, write separate tests.

Project-Specific Rules

No async markers needed

This project uses asyncio_mode = "auto" globally. Write async tests without decorators:

# Correct
async def test_async_operation():
    result = await some_async_function()
    assert result == expected


# Wrong - don't add this
@pytest.mark.asyncio
async def test_async_operation(): ...

Imports at module level

Put ALL imports at the top of the file:

# Correct
import pytest
from fastmcp import FastMCP
from fastmcp.client import Client


async def test_something():
    mcp = FastMCP("test")
    ...


# Wrong - no local imports
async def test_something():
    from fastmcp import FastMCP  # Don't do this

    ...

Use in-memory transport for testing

Pass FastMCP servers directly to clients:

from fastmcp import FastMCP
from fastmcp.client import Client

mcp = FastMCP("TestServer")


@mcp.tool
def greet(name: str) -> str:
    return f"Hello, {name}!"


async def test_greet_tool():
    async with Client(mcp) as client:
        result = await client.call_tool("greet", {"name": "World"})
        assert result.data == "Hello, World!"

CONSTRAINT — API version: result.data is the FastMCP v3+ accessor (including v4). The legacy v2 pattern result[0].text is incorrect for v3+ and will raise an AttributeError. Never use result[0].text in new tests.

Only use HTTP transport when explicitly testing network features.

Inline snapshots for complex data

Use inline-snapshot for testing JSON schemas and complex structures:

from inline_snapshot import snapshot


def test_schema_generation():
    schema = generate_schema(MyModel)
    assert schema == snapshot()  # Will auto-populate on first run

Commands:

  • pytest -n 2 --inline-snapshot=create - populate empty snapshots
  • pytest -n 2 --inline-snapshot=fix - update after intentional changes

Fixtures

Prefer function-scoped fixtures

@pytest.fixture
def client():
    return Client()


async def test_with_client(client):
    result = await client.ping()
    assert result is not None

Use tmp_path for file operations

def test_file_writing(tmp_path):
    file = tmp_path / "test.txt"
    file.write_text("content")
    assert file.read_text() == "content"

Mocking

Mock at the boundary

from unittest.mock import patch, AsyncMock


async def test_external_api_call():
    with patch("mymodule.external_client.fetch", new_callable=AsyncMock) as mock:
        mock.return_value = {"data": "test"}
        result = await my_function()
        assert result == {"data": "test"}

Don't mock what you own

Test your code with real implementations when possible. Mock external services, not internal classes.

Test Naming

Use descriptive names that explain the scenario:

# Good
def test_login_fails_with_invalid_password():
def test_user_can_update_own_profile():
def test_admin_can_delete_any_user():

# Bad
def test_login():
def test_update():
def test_delete():

Error Testing

import pytest


def test_raises_on_invalid_input():
    with pytest.raises(ValueError, match="must be positive"):
        calculate(-1)


async def test_async_raises():
    with pytest.raises(ConnectionError):
        await connect_to_invalid_host()

Running Tests

uv run pytest -n 2                      # Run all tests in parallel
uv run pytest -n 2 -x                   # Stop on first failure
uv run pytest -n 2 path/to/test.py      # Run specific file
uv run pytest -n 2 -k "test_name"       # Run tests matching pattern
uv run pytest -n 2 -m "not integration" # Exclude integration tests

Checklist

Before submitting tests:

  • Each test tests one thing
  • No @pytest.mark.asyncio decorators
  • Imports at module level
  • Descriptive test names
  • Using in-memory transport (not HTTP) unless testing networking
  • Parameterization for variations of same behavior
  • Separate tests for different behaviors

References

  1. FastMCP Testing (accessed 2026-06-07)