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 current GraphQLConfig struct; 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 GraphQLConfig accepts 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_dir config
  • Upstream Passthrough: Optionally proxy queries to a real GraphQL server (upstream_url)
  • Built-in Playground: GraphQL Playground served at the same /graphql endpoint

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

  1. Use Descriptive Names: Choose clear, self-documenting field names
  2. Follow Conventions: Use camelCase for fields, PascalCase for types
  3. Document Your Schema: Add descriptions to types and fields
  4. Version Carefully: Use field deprecation instead of breaking changes

Performance

  1. Implement Caching: Cache expensive resolver operations
  2. Limit Query Depth: Prevent deeply nested queries
  3. Use DataLoaders: Batch and cache data fetching
  4. Monitor Complexity: Track query complexity metrics

Testing

  1. Test Query Variations: Test different query structures and variables
  2. Validate Error Cases: Ensure proper error handling
  3. Test Subscriptions: Verify real-time functionality
  4. 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.