GraphQL Mocking
MockForge provides GraphQL API mocking capabilities — schema-driven response generation, introspection support, custom resolvers, and a built-in Playground.
Status legend used in this chapter:
- Implemented (default) — sections describing schema, introspection, playground, custom resolvers, automatic response generation, basic error responses, and the upstream-passthrough config field.
- Draft / aspirational — sections describing GraphQL subscriptions, schema stitching, query-complexity caps, dedicated GraphQL caching, and
graphql.performance.*config blocks. The example YAML for these isn’t wired into the currentGraphQLConfigstruct; treat them as roadmap. For cross-cutting concerns (latency, fault injection, rate limiting, response truncation), use the chaos engine — it covers every protocol on the same listener.The configuration reference under “Configuration” below lists exactly what
GraphQLConfigaccepts today.
Overview
MockForge’s GraphQL support includes:
- Schema-Driven Mocking: Generate responses based on GraphQL schema definitions
- Introspection Support: Full GraphQL introspection query support
- Custom Resolvers: Implement custom logic for specific fields via the
handlers_dirconfig - Upstream Passthrough: Optionally proxy queries to a real GraphQL server (
upstream_url) - Built-in Playground: GraphQL Playground served at the same
/graphqlendpoint
Getting Started
Basic Setup
Enable GraphQL mocking in your MockForge configuration:
# config.yaml
graphql:
enabled: true
endpoint: "/graphql"
schema_file: "schema.graphql"
introspection: true
playground: true
server:
http_port: 3000
Start MockForge with GraphQL support:
mockforge serve --config config.yaml
Access your GraphQL endpoint:
- GraphQL Endpoint:
http://localhost:3000/graphql - GraphQL Playground:
http://localhost:3000/graphql/playground
Schema Definition
Create a GraphQL schema file:
# schema.graphql
type User {
id: ID!
name: String!
email: String!
age: Int
posts: [Post!]!
profile: UserProfile
}
type Post {
id: ID!
title: String!
content: String!
published: Boolean!
author: User!
createdAt: String!
tags: [String!]!
}
type UserProfile {
bio: String
website: String
location: String
avatarUrl: String
}
type Query {
users: [User!]!
user(id: ID!): User
posts: [Post!]!
post(id: ID!): Post
searchUsers(query: String!): [User!]!
}
type Mutation {
createUser(input: CreateUserInput!): User!
updateUser(id: ID!, input: UpdateUserInput!): User!
deleteUser(id: ID!): Boolean!
createPost(input: CreatePostInput!): Post!
}
type Subscription {
userCreated: User!
postPublished: Post!
userOnline(userId: ID!): Boolean!
}
input CreateUserInput {
name: String!
email: String!
age: Int
}
input UpdateUserInput {
name: String
email: String
age: Int
}
input CreatePostInput {
title: String!
content: String!
authorId: ID!
tags: [String!]
}
Configuration
The full set of YAML config knobs the GraphQL server reads:
graphql:
enabled: true # Start the GraphQL server
port: 4000 # Listener port
host: "0.0.0.0" # Bind address
schema_path: "schema.graphql" # Path to .graphql / .gql schema file
handlers_dir: "./resolvers" # Optional directory of custom resolvers
playground_enabled: true # Serve GraphQL Playground at /graphql
introspection_enabled: true # Allow introspection queries
upstream_url: null # Optional: passthrough to a real GraphQL server
The endpoint path is fixed at /graphql (and Playground is served at the
same path when playground_enabled is true; GET → Playground UI, POST →
GraphQL execution).
CLI flags
mockforge serve --spec api.yaml \
--graphql-port 4000 \
--graphql # turn on the server even if config has it disabled
For features that aren’t yet first-class GraphQL config (latency injection,
fault injection, rate limiting, query-complexity caps, response caching),
use the chaos engine — it covers every protocol
on the same listener config surface, so a latency.fixed_delay_ms: 100
under observability.chaos adds 100 ms to every GraphQL response just as
it does to HTTP and gRPC.
Response Generation
Automatic Response Generation
MockForge automatically generates realistic responses based on your schema:
# Query
query GetUsers {
users {
id
name
email
age
posts {
title
published
}
}
}
{
"data": {
"users": [
{
"id": "1a2b3c4d",
"name": "Alice Johnson",
"email": "[email protected]",
"age": 29,
"posts": [
{
"title": "Getting Started with GraphQL",
"published": true
},
{
"title": "Advanced Query Techniques",
"published": false
}
]
},
{
"id": "2b3c4d5e",
"name": "Bob Smith",
"email": "[email protected]",
"age": 34,
"posts": [
{
"title": "Building Scalable APIs",
"published": true
}
]
}
]
}
}
Template-Based Responses
Use templates for more control over response data:
# graphql/responses/user.yaml
query: "query GetUser($id: ID!)"
response:
data:
user:
id: "{{args.id}}"
name: "{{faker.name.fullName}}"
email: "{{faker.internet.email}}"
age: "{{randInt 18 65}}"
profile:
bio: "{{faker.lorem.sentence}}"
website: "{{faker.internet.url}}"
location: "{{faker.address.city}}, {{faker.address.state}}"
avatarUrl: "https://api.dicebear.com/7.x/avataaars/svg?seed={{uuid}}"
Custom Field Resolvers
Create custom resolvers for specific fields:
// graphql/resolvers/user.js
module.exports = {
User: {
// Custom resolver for posts field
posts: (parent, args, context) => {
return context.dataSources.posts.getByAuthorId(parent.id);
},
// Computed field
fullName: (parent) => {
return `${parent.firstName} ${parent.lastName}`;
},
// Async resolver with external data
socialStats: async (parent, args, context) => {
return await context.dataSources.social.getStats(parent.id);
}
},
Query: {
// Custom query resolver
searchUsers: (parent, args, context) => {
const { query, limit = 10 } = args;
return context.dataSources.users.search(query, limit);
}
},
Mutation: {
// Custom mutation resolver
createUser: (parent, args, context) => {
const { input } = args;
const user = {
id: uuid(),
...input,
createdAt: new Date().toISOString()
};
context.dataSources.users.create(user);
// Trigger subscription
context.pubsub.publish('USER_CREATED', { userCreated: user });
return user;
}
}
};
Data Sources
CSV Data Source
Connect GraphQL resolvers to CSV data:
# config.yaml
graphql:
data_sources:
users:
type: "csv"
file: "data/users.csv"
key_field: "id"
posts:
type: "csv"
file: "data/posts.csv"
key_field: "id"
relationships:
author_id: "users.id"
# data/users.csv
id,name,email,age
1,Alice Johnson,[email protected],29
2,Bob Smith,[email protected],34
3,Carol Davis,[email protected],27
REST API Data Source
Fetch data from external REST APIs:
graphql:
data_sources:
users:
type: "rest"
base_url: "https://jsonplaceholder.typicode.com"
endpoints:
getAll: "/users"
getById: "/users/{id}"
create:
method: "POST"
url: "/users"
posts:
type: "rest"
base_url: "https://jsonplaceholder.typicode.com"
endpoints:
getAll: "/posts"
getByUserId: "/posts?userId={userId}"
Database Data Source
Connect to databases for realistic data:
graphql:
data_sources:
database:
type: "postgresql"
connection_string: "postgresql://user:pass@localhost/mockdb"
tables:
users:
table: "users"
key_field: "id"
posts:
table: "posts"
key_field: "id"
relationships:
author_id: "users.id"
Subscriptions
WebSocket Subscriptions
Enable real-time GraphQL subscriptions:
graphql:
subscriptions:
enabled: true
transport: "websocket"
endpoint: "/graphql/ws"
heartbeat_interval: 30
connection_timeout: 60
Subscription Resolvers
// graphql/resolvers/subscriptions.js
module.exports = {
Subscription: {
userCreated: {
subscribe: (parent, args, context) => {
return context.pubsub.asyncIterator('USER_CREATED');
}
},
postPublished: {
subscribe: (parent, args, context) => {
return context.pubsub.asyncIterator('POST_PUBLISHED');
}
},
userOnline: {
subscribe: (parent, args, context) => {
const { userId } = args;
return context.pubsub.asyncIterator(`USER_ONLINE_${userId}`);
}
}
}
};
Triggering Subscriptions
Trigger subscriptions from mutations or external events:
// In mutation resolver
createPost: (parent, args, context) => {
const post = createNewPost(args.input);
// Trigger subscription
context.pubsub.publish('POST_PUBLISHED', {
postPublished: post
});
return post;
}
Schema Stitching
Combine multiple GraphQL schemas:
graphql:
schema_stitching:
enabled: true
schemas:
- name: "users"
file: "schemas/users.graphql"
endpoint: "http://users-service/graphql"
- name: "posts"
file: "schemas/posts.graphql"
endpoint: "http://posts-service/graphql"
- name: "comments"
file: "schemas/comments.graphql"
endpoint: "http://comments-service/graphql"
# Type extensions for stitching
extensions:
- |
extend type User {
posts: [Post]
}
- |
extend type Post {
comments: [Comment]
}
Error Handling
Custom Error Responses
Configure custom error handling:
graphql:
errors:
# Include detailed error information
include_stack_trace: true
include_extensions: true
# Custom error codes
custom_error_codes:
INVALID_INPUT: 400
UNAUTHORIZED: 401
FORBIDDEN: 403
NOT_FOUND: 404
RATE_LIMITED: 429
Error Response Format
{
"errors": [
{
"message": "User not found",
"locations": [
{
"line": 2,
"column": 3
}
],
"path": ["user"],
"extensions": {
"code": "NOT_FOUND",
"userId": "invalid-id",
"timestamp": "2024-01-01T00:00:00Z"
}
}
],
"data": {
"user": null
}
}
Performance & Optimization
Query Complexity Analysis
Prevent expensive queries:
graphql:
performance:
enable_query_complexity_analysis: true
max_query_depth: 10
max_query_complexity: 1000
complexity_scalarCost: 1
complexity_objectCost: 2
complexity_listFactor: 10
complexity_introspectionCost: 100
Caching
Cache responses for improved performance:
graphql:
caching:
enabled: true
ttl_seconds: 300
max_cache_size: 1000
cache_key_strategy: "query_and_variables"
# Cache per resolver
resolver_cache:
"Query.users": 600 # Cache for 10 minutes
"Query.posts": 300 # Cache for 5 minutes
Latency Simulation
Simulate real-world latency:
graphql:
latency:
enabled: true
default_delay_ms: 100
# Per-field latency
field_delays:
"Query.users": 200
"User.posts": 150
"Post.comments": 100
# Random latency ranges
random_delay:
min_ms: 50
max_ms: 500
Testing & Development
GraphQL Playground
The built-in GraphQL Playground provides:
- Interactive Query Editor: Write and test GraphQL queries
- Schema Documentation: Browse your schema structure
- Query Variables: Test with different variable values
- Response Headers: View response metadata
- Subscription Testing: Test real-time subscriptions
Query Examples
Test your GraphQL API with these examples:
# Simple query
query GetAllUsers {
users {
id
name
email
}
}
# Query with variables
query GetUser($userId: ID!) {
user(id: $userId) {
id
name
email
posts {
title
published
}
}
}
# Mutation
mutation CreateUser($input: CreateUserInput!) {
createUser(input: $input) {
id
name
email
}
}
# Subscription
subscription UserUpdates {
userCreated {
id
name
email
}
}
Integration with HTTP Mocking
Combine GraphQL with REST API mocking:
# config.yaml
http:
enabled: true
spec: "openapi.yaml"
graphql:
enabled: true
schema_file: "schema.graphql"
# Use REST endpoints in GraphQL resolvers
graphql:
data_sources:
rest_api:
type: "rest"
base_url: "http://localhost:3000" # MockForge HTTP server
endpoints:
users: "/api/users"
posts: "/api/posts"
Best Practices
Schema Design
- Use Descriptive Names: Choose clear, self-documenting field names
- Follow Conventions: Use camelCase for fields, PascalCase for types
- Document Your Schema: Add descriptions to types and fields
- Version Carefully: Use field deprecation instead of breaking changes
Performance
- Implement Caching: Cache expensive resolver operations
- Limit Query Depth: Prevent deeply nested queries
- Use DataLoaders: Batch and cache data fetching
- Monitor Complexity: Track query complexity metrics
Testing
- Test Query Variations: Test different query structures and variables
- Validate Error Cases: Ensure proper error handling
- Test Subscriptions: Verify real-time functionality
- Performance Testing: Test with realistic query loads
Troubleshooting
Common Issues
Schema Loading Errors
# Validate GraphQL schema
mockforge graphql validate --schema schema.graphql
# Check schema syntax
graphql-schema-linter schema.graphql
Resolver Errors
# Enable debug logging
RUST_LOG=mockforge_graphql=debug mockforge serve
# Test individual resolvers
mockforge graphql test-resolver Query.users
Subscription Issues
# Test WebSocket connection
wscat -c ws://localhost:3000/graphql/ws
# Check subscription resolver
mockforge graphql test-subscription userCreated
This comprehensive GraphQL support makes MockForge a powerful tool for mocking modern GraphQL APIs with realistic data and behavior.