Schema Federation Testing
The Federation Challenge
When using Apollo Federation or schema stitching, multiple subgraph services contribute types and fields to a composed supergraph. Testing must verify that the composition is correct, that entity references resolve across subgraph boundaries, and that there are no conflicting type definitions.
Federation Architecture
+------------------+ +------------------+ +------------------+
| ProductService | | OrderService | | UserService |
| Subgraph | | Subgraph | | Subgraph |
| | | | | |
| type Product | | type Order | | type User |
| @key(id) | | items: [Item] | | @key(id) |
| name: String | | customer: User | | name: String |
| price: Float | | | | email: String |
+--------+---------+ +--------+---------+ +--------+---------+
| | |
+---------------------+---------------------+
|
+----------+----------+
| Apollo Gateway |
| (Composed Schema) |
+---------------------+
Test 1: All Subgraph Types Are Resolvable
class TestSchemaFederation:
"""Test that federated GraphQL schemas compose correctly."""
def test_all_subgraph_types_resolvable(self, composed_schema, subgraphs):
"""Every type defined in a subgraph should be resolvable
in the composed schema."""
for subgraph_name, subgraph_schema in subgraphs.items():
for type_name in subgraph_schema.get_types():
if type_name.startswith("__"): # Skip introspection types
continue
assert type_name in composed_schema.get_types(), (
f"Type '{type_name}' from subgraph '{subgraph_name}' "
f"is not resolvable in the composed schema"
)
Test 2: Entity References Resolve Across Subgraphs
def test_entity_references_resolve(self, gateway_client):
"""Entity references across subgraphs should resolve without errors."""
# Product is defined in ProductService, but Order references it
query = """
query {
order(id: "order-123") {
id
items {
product {
id
name # Resolved from ProductService
price # Resolved from ProductService
}
quantity
}
}
}
"""
response = gateway_client.execute(query)
assert "errors" not in response, (
f"Entity resolution failed: {response.get('errors')}"
)
assert response["data"]["order"]["items"][0]["product"]["name"] is not None
Test 3: No Conflicting Type Definitions
When two subgraphs extend the same type, their field definitions must be compatible:
def test_no_conflicting_type_definitions(self, subgraphs):
"""Subgraphs should not define conflicting field types for shared types."""
type_fields = {}
conflicts = []
for subgraph_name, schema in subgraphs.items():
for type_name, type_def in schema.get_types().items():
if type_name.startswith("__"):
continue
if type_name not in type_fields:
type_fields[type_name] = {}
for field_name, field_type in type_def.fields.items():
if field_name in type_fields[type_name]:
existing = type_fields[type_name][field_name]
if field_type != existing["type"]:
conflicts.append(
f"Field '{type_name}.{field_name}' has conflicting types: "
f"'{existing['type']}' in {existing['subgraph']} vs "
f"'{field_type}' in {subgraph_name}"
)
type_fields[type_name][field_name] = {
"type": field_type,
"subgraph": subgraph_name
}
assert not conflicts, (
f"Found {len(conflicts)} type conflicts:\n" + "\n".join(conflicts)
)
Test 4: Subgraph Health and Availability
def test_all_subgraphs_healthy(self, subgraph_endpoints):
"""Each subgraph should respond to health checks."""
for name, url in subgraph_endpoints.items():
response = httpx.get(f"{url}/.well-known/apollo/server-health")
assert response.status_code == 200, (
f"Subgraph '{name}' at {url} is unhealthy: {response.status_code}"
)
def test_gateway_introspection(self, gateway_client):
"""Gateway should respond to introspection queries."""
query = """
{
__schema {
types { name }
queryType { name }
}
}
"""
response = gateway_client.execute(query)
assert "errors" not in response
type_names = [t["name"] for t in response["data"]["__schema"]["types"]]
# Verify key types from each subgraph are present
assert "Product" in type_names
assert "Order" in type_names
assert "User" in type_names
Test 5: Cross-Subgraph Query Performance
def test_cross_subgraph_query_latency(self, gateway_client):
"""Queries spanning multiple subgraphs should meet latency SLA."""
# This query touches all three subgraphs
query = """
query {
user(id: "user-1") {
name # UserService
orders(first: 5) { # OrderService
id
total
items {
product {
name # ProductService
price
}
}
}
}
}
"""
import time
start = time.monotonic()
response = gateway_client.execute(query)
duration = time.monotonic() - start
assert "errors" not in response
assert duration < 3.0, (
f"Cross-subgraph query took {duration:.2f}s, SLA is 3.0s. "
f"Check query plan and subgraph latencies."
)
AI-Generated Federation Tests
Ask AI to generate federation tests from your subgraph schemas:
Given the following Apollo Federation subgraph schemas, generate tests
that verify correct composition:
Subgraph 1 (ProductService):
type Product @key(fields: "id") {
id: ID!
name: String!
price: Float!
category: Category!
}
Subgraph 2 (InventoryService):
extend type Product @key(fields: "id") {
id: ID! @external
inStock: Boolean!
warehouseLocation: String
}
Subgraph 3 (ReviewService):
extend type Product @key(fields: "id") {
id: ID! @external
reviews: [Review!]!
averageRating: Float
}
Generate tests for:
1. Each subgraph's fields resolve correctly through the gateway
2. Cross-subgraph queries work (e.g., product with stock AND reviews)
3. Entity reference resolution (no null references)
4. Performance: cross-subgraph queries under 2s
5. Error handling: what happens when one subgraph is down?
Graceful Degradation Testing
def test_gateway_handles_subgraph_failure(self, gateway_client, subgraph_controller):
"""Gateway should return partial data when one subgraph is down."""
# Stop the ReviewService subgraph
subgraph_controller.stop("ReviewService")
query = """
query {
product(id: "prod-1") {
name # From ProductService (available)
price # From ProductService (available)
reviews { # From ReviewService (unavailable)
rating
}
}
}
"""
response = gateway_client.execute(query)
# Product data should still be available
assert response["data"]["product"]["name"] is not None
assert response["data"]["product"]["price"] is not None
# Reviews should be null or have an error
assert (
response["data"]["product"]["reviews"] is None
or "errors" in response
)
# Restart for other tests
subgraph_controller.start("ReviewService")
Key Takeaway
Federation testing verifies that independently-developed subgraphs compose correctly into a functioning supergraph. The critical tests are: type resolution across subgraph boundaries, conflict detection between shared types, cross-subgraph query performance, and graceful degradation when a subgraph fails. AI can generate these tests from subgraph SDL schemas, covering composition correctness and entity reference resolution systematically.