backend-api-test
Guidelines for writing backend API tests using pytest and FastAPI TestClient. Use this skill when writing or modifying tests in tests/backend directory.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Guidelines for writing backend API tests using pytest and FastAPI TestClient. Use this skill when writing or modifying tests in tests/backend directory.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
| name | backend-api-test |
| description | Guidelines for writing backend API tests using pytest and FastAPI TestClient. Use this skill when writing or modifying tests in tests/backend directory. |
This skill provides comprehensive guidelines for writing backend API tests for the Factory Inventory Management System. Follow these patterns to ensure consistent, thorough test coverage.
All backend tests must be placed in tests/backend/:
tests/backend/
├── conftest.py # Shared fixtures and test client setup
├── test_inventory.py # Inventory endpoint tests
├── test_orders.py # Orders endpoint tests
├── test_dashboard.py # Dashboard endpoint tests
└── test_misc_endpoints.py # Other endpoint tests
test_<feature>.py format (e.g., test_inventory.py, test_orders.py)Organize tests within a class using descriptive names:
"""
Tests for <feature> API endpoints.
"""
import pytest
class Test<Feature>Endpoints:
"""Test suite for <feature>-related endpoints."""
def test_get_all_<resources>(self, client):
"""Test getting all <resources>."""
# Test implementation
Always test the happy path first:
def test_get_all_orders(self, client):
"""Test getting all orders."""
response = client.get("/api/orders")
assert response.status_code == 200
data = response.json()
assert isinstance(data, list)
assert len(data) > 0
# Verify structure of first item
first_order = data[0]
assert "id" in first_order
assert "order_number" in first_order
# ... other required fields
Test each query parameter filter individually and in combination:
def test_get_orders_by_warehouse(self, client):
"""Test filtering orders by warehouse."""
response = client.get("/api/orders?warehouse=Tokyo")
assert response.status_code == 200
data = response.json()
assert isinstance(data, list)
# Verify all results match the filter
for order in data:
assert order["warehouse"] == "Tokyo"
def test_get_orders_multiple_filters(self, client):
"""Test filtering orders with multiple filters."""
response = client.get(
"/api/orders?warehouse=San Francisco&category=Power Supplies&status=Delivered"
)
assert response.status_code == 200
data = response.json()
# Verify all results match ALL filters
for order in data:
assert order["warehouse"] == "San Francisco"
assert order["category"].lower() == "power supplies"
assert order["status"].lower() == "delivered"
Common filters to test:
warehouse - Filter by warehouse locationcategory - Filter by product categorystatus - Filter by order status (orders only)month - Filter by month in format YYYY-MM or quarter Q1-2025Test fetching individual resources by ID:
def test_get_order_by_id(self, client):
"""Test getting a specific order by ID."""
# First get all orders to find a valid ID
response = client.get("/api/orders")
all_orders = response.json()
assert len(all_orders) > 0
first_order_id = all_orders[0]["id"]
# Now get that specific order
response = client.get(f"/api/orders/{first_order_id}")
assert response.status_code == 200
order = response.json()
assert order["id"] == first_order_id
def test_get_nonexistent_order(self, client):
"""Test getting an order that doesn't exist."""
response = client.get("/api/orders/nonexistent-order-999")
assert response.status_code == 404
data = response.json()
assert "detail" in data
assert "not found" in data["detail"].lower()
Verify the response structure matches the API contract:
def test_order_items_structure(self, client):
"""Test that order items have proper structure."""
response = client.get("/api/orders")
data = response.json()
for order in data:
assert "items" in order
assert isinstance(order["items"], list)
for item in order["items"]:
assert "sku" in item
assert "name" in item
assert "quantity" in item
assert "unit_price" in item
assert isinstance(item["quantity"], int)
assert isinstance(item["unit_price"], (int, float))
Ensure numeric fields have correct types and valid ranges:
def test_inventory_quantity_types(self, client):
"""Test that quantity fields are proper numeric types."""
response = client.get("/api/inventory")
data = response.json()
for item in data:
assert isinstance(item["quantity_on_hand"], int)
assert isinstance(item["reorder_point"], int)
assert isinstance(item["unit_cost"], (int, float))
assert item["quantity_on_hand"] >= 0
assert item["reorder_point"] >= 0
assert item["unit_cost"] >= 0
Test calculated values and business rules:
def test_order_total_value_calculation(self, client):
"""Test that order total values are reasonable."""
response = client.get("/api/orders")
data = response.json()
for order in data:
assert "total_value" in order
assert isinstance(order["total_value"], (int, float))
assert order["total_value"] > 0
# Verify total makes sense based on items
calculated_total = sum(
item["quantity"] * item["unit_price"]
for item in order["items"]
)
# Allow small floating point differences
assert abs(order["total_value"] - calculated_total) < 0.01
Verify constrained fields have valid values:
def test_order_status_values(self, client):
"""Test that orders have valid status values."""
response = client.get("/api/orders")
data = response.json()
valid_statuses = ["delivered", "shipped", "processing", "backordered"]
for order in data:
assert order["status"].lower() in valid_statuses
Verify date fields are properly formatted:
def test_order_dates_format(self, client):
"""Test that order dates are in proper format."""
response = client.get("/api/orders")
data = response.json()
for order in data:
assert "order_date" in order
assert "expected_delivery" in order
# Date should contain year, month pattern (ISO format)
assert "2025-" in order["order_date"]
assert "-" in order["expected_delivery"]
assert "T" in order["expected_delivery"] # Has time component
Test that aggregated endpoints match raw data:
def test_dashboard_pending_orders_calculation(self, client):
"""Test that pending orders are calculated correctly."""
# Get all orders
orders_response = client.get("/api/orders")
all_orders = orders_response.json()
# Count processing and backordered orders
pending_count = sum(
1 for order in all_orders
if order["status"].lower() in ["processing", "backordered"]
)
# Get dashboard summary
dashboard_response = client.get("/api/dashboard/summary")
dashboard_data = dashboard_response.json()
assert dashboard_data["pending_orders"] == pending_count
client - FastAPI TestClient instance (required for all tests)sample_inventory_item - Example inventory item structuresample_order - Example order structuredef test_example(self, client):
"""Every test method needs the client fixture."""
response = client.get("/api/endpoint")
assert response.status_code == 200
Add shared fixtures to conftest.py:
@pytest.fixture
def sample_warehouse_data():
"""Sample warehouse data for testing."""
return {
"name": "San Francisco",
"location": "CA",
# ... other fields
}
Use descriptive names that clearly indicate what is being tested:
test_get_all_<resources> - Get all items without filterstest_get_<resource>_by_<filter> - Single filter teststest_get_<resource>_multiple_filters - Combined filter teststest_get_<resource>_by_id - Single item retrievaltest_get_nonexistent_<resource> - 404 handlingtest_<resource>_<field>_structure - Data structure validationtest_<resource>_<field>_types - Data type validationtest_<resource>_<calculation>_calculation - Business logicassert response.status_code == 200 # Success
assert response.status_code == 404 # Not found
assert response.status_code == 422 # Validation error
data = response.json()
assert isinstance(data, list) # Array response
assert isinstance(data, dict) # Object response
assert len(data) > 0 # Has data
assert "field_name" in data
assert "detail" in error_response # Error messages
assert order["status"].lower() == "delivered"
assert item["category"].lower() == "power supplies"
# Allow small differences for float calculations
assert abs(calculated - expected) < 0.01
GET /api/inventory - All inventory items
warehouse, categoryGET /api/inventory/{id} - Single inventory itemGET /api/orders - All orders
warehouse, category, status, monthGET /api/orders/{id} - Single orderGET /api/dashboard/summary - Dashboard summary
warehouse, category, status, monthGET /api/demand - Demand forecast (no filters)GET /api/backlog - Backlog items (no filters)GET /api/spending/* - Spending data endpoints2025-01, 2025-02, etc.Q1-2025, Q2-2025, etc.allabs(a - b) < 0.01 for money calculations# Run all backend tests
pytest tests/backend/
# Run specific test file
pytest tests/backend/test_orders.py
# Run specific test class
pytest tests/backend/test_orders.py::TestOrdersEndpoints
# Run specific test method
pytest tests/backend/test_orders.py::TestOrdersEndpoints::test_get_all_orders
# Run with verbose output
pytest tests/backend/ -v
# Run with coverage
pytest tests/backend/ --cov=server
"""
Tests for <feature> API endpoints.
"""
import pytest
class Test<Feature>Endpoints:
"""Test suite for <feature>-related endpoints."""
def test_get_all_<resources>(self, client):
"""Test getting all <resources>."""
response = client.get("/api/<resources>")
assert response.status_code == 200
data = response.json()
assert isinstance(data, list)
assert len(data) > 0
# Verify structure
first_item = data[0]
assert "id" in first_item
# Add other required fields
def test_get_<resource>_by_filter(self, client):
"""Test filtering <resources> by <filter>."""
response = client.get("/api/<resources>?<filter>=<value>")
assert response.status_code == 200
data = response.json()
# Verify filter applied correctly
for item in data:
assert item["<filter>"] == "<value>"
def test_get_<resource>_by_id(self, client):
"""Test getting a specific <resource> by ID."""
# Get valid ID first
response = client.get("/api/<resources>")
all_items = response.json()
item_id = all_items[0]["id"]
# Get specific item
response = client.get(f"/api/<resources>/{item_id}")
assert response.status_code == 200
item = response.json()
assert item["id"] == item_id
def test_get_nonexistent_<resource>(self, client):
"""Test getting a <resource> that doesn't exist."""
response = client.get("/api/<resources>/nonexistent-999")
assert response.status_code == 404
data = response.json()
assert "detail" in data
assert "not found" in data["detail"].lower()
client fixture - It's the TestClient for making API callsisinstance() for proper type checking