StackPractices
beginner Por Mathias Paulenko

Mocks de resolvers GraphQL para desarrollo frontend

Configura resolvers GraphQL mockeados con Apollo Server para que los equipos frontend desarrollen contra una API falsa antes del backend.

Visión General

¿Backend no listo? Los equipos frontend se quedan idle esperando contratos de API. El mocking integrado de Apollo Server lo soluciona — auto-genera datos falsos para cada campo del schema. Los devs de UI obtienen un endpoint GraphQL funcional en minutos, construyen contra él e intercambian los auto-mocks por resolvers personalizados conforme el schema se estabiliza.

La primera vez que usé este patrón fue en un proyecto donde el equipo de backend estaba seis semanas atrasado con el schema GraphQL. En vez de esperar, levantamos un Apollo Server con mocks: true en unos veinte minutos. El equipo frontend construyó toda la página de lista de usuarios contra datos falsos, y cuando los resolvers reales arrivaron, los intercambiamos con preserveResolvers: true — sin cambios en la UI. Esa experiencia me convenció de que el mocking es una herramienta de workflow, no solo de testing.

Esta receta cubre @apollo/server 4.x con @faker-js/faker 8.x para datos falsos realistas. Los mismos conceptos aplican a MSW 2.x cuando preferís interceptar a nivel del browser.

Cuándo Usarlo

Usá Apollo mocking cuando frontend y backend se construyen en paralelo, cuando necesitás un endpoint GraphQL corriendo para una demo o prototipo, o cuando querés testear componentes de UI contra shapes de datos realistas. También sirve para simular estados de error — lanzá errores desde resolvers específicos y mirá cómo reacciona la UI antes de que el servicio real exista. Si ya tenés un Apollo Server corriendo, el mocking se agrega encima sin cambiar tu schema.

Cuándo NO Usarlo

No lo uses cuando el backend ya esté disponible y necesitás validación de contrato end-to-end; ahí conviene Integration Testing. Para mockear HTTP a nivel de browser sin un servidor, MSW va mejor. Y si el schema cambia muy rápido, los mocks pueden ocultar breaking changes. Para patrones de manejo de errores en producción, ver GraphQL Error Handling Best Practices.

Solución

Habilitar mocking integrado

// mock-server.ts
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';

const typeDefs = gql`
  type User {
    id: ID!
    name: String!
    email: String!
    role: String!
  }

  type Post {
    id: ID!
    title: String!
    content: String!
    author: User!
    publishedAt: String!
  }

  type Query {
    users: [User!]!
    user(id: ID!): User
    posts: [Post!]!
  }
`;

const server = new ApolloServer({
  typeDefs,
  mocks: true,
});

const { url } = await startStandaloneServer(server, {
  listen: { port: 4000 },
});

console.log(`Mock server ready at ${url}`);

Apollo genera valores automáticamente según el tipo escalar: strings aleatorios para String, números incrementales para Int e ID, y timestamps ISO para campos con nombres de fechas.

Personalizar mocks con escalares

import { ApolloServer } from '@apollo/server';

const mocks = {
  ID: () => crypto.randomUUID(),
  String: () => 'Lorem ipsum',
  Int: () => Math.floor(Math.random() * 1000),
  Boolean: () => Math.random() > 0.5,
};

const server = new ApolloServer({
  typeDefs,
  mocks,
});

Mockear tipos y campos específicos

const mocks = {
  User: () => ({
    id: () => crypto.randomUUID(),
    name: () => faker.person.fullName(),
    email: () => faker.internet.email(),
    role: () => faker.helpers.arrayElement(['admin', 'editor', 'viewer']),
  }),

  Post: () => ({
    id: () => crypto.randomUUID(),
    title: () => faker.lorem.sentence(),
    content: () => faker.lorem.paragraphs(3),
    publishedAt: () => faker.date.recent().toISOString(),
  }),

  Query: () => ({
    users: () => Array.from({ length: 5 }, () => ({})),
    posts: () => Array.from({ length: 10 }, () => ({})),
  }),
};

const server = new ApolloServer({
  typeDefs,
  mocks,
});

Alternar mocking por entorno

const server = new ApolloServer({
  typeDefs,
  resolvers: process.env.NODE_ENV === 'production' ? realResolvers : undefined,
  mocks: process.env.MOCK_API === 'true',
});

// O combinar resolvers reales con fallback a mocks
const server = new ApolloServer({
  typeDefs,
  resolvers: realResolvers,
  mocks: process.env.NODE_ENV === 'development'
    ? { mocks, preserveResolvers: true }
    : false,
});

Con preserveResolvers: true, Apollo usa tus resolvers reales donde existen y recurre a mocks para los campos no implementados.

Explicación

flowchart diagram: Schema GraphQL

El auto-mocking de Apollo inspecciona el schema y genera un valor por defecto para cada escalar, así que strings, números, booleanos y listas se completan automáticamente. Podés sobreescribir esos valores por defecto con funciones de mock personalizadas por tipo o escalar. Con un mock de User, cada campo tiene su propio generador — así decidís exactamente qué devuelven name, email y role.

La opción preserveResolvers es lo que hace que las migraciones incrementales sean indoloras. Los campos con resolver conservan su implementación real; los que no, caen en el mock. Así shippeás el backend pieza por pieza — el frontend nunca nota la diferencia.

La integración con Faker produce nombres, emails, oraciones y fechas realistas, así que la UI se ve y se comporta como si estuviera conectada a datos reales.

Un trade-off que me mordió: los datos mock pueden dar falsa confianza. Si tu mock siempre devuelve una lista de 10 ítems, no vas a detectar el bug que aparece cuando la lista está vacía. Por eso siempre mockeo edge cases explícitamente — listas vacías, campos null, respuestas de error y strings inusualmente largos. Es tentador saltearse eso, pero los diez minutos que gastás en mocks de edge cases te van a ahorrar horas de debugging después.

Otro gotcha: @apollo/server 4.x cambió la API de mocking respecto a Apollo Server 3.x. En 3.x, pasabas mocks al constructor de ApolloServer con mockEntireSchema: false para preservar resolvers. En 4.x, la opción es preserveResolvers: true dentro del objeto mocks. Si estás migrando, revisá bien tu configuración de mock — los mensajes de error no siempre son obvios cuando la API no coincide.

Variantes

Scalares y enums personalizados

Cuando tu schema usa escalares o enums personalizados, proveé funciones de mock explícitas:

import { ApolloServer } from '@apollo/server';

const typeDefs = gql`
  enum Role {
    ADMIN
    EDITOR
    VIEWER
  }

  scalar Date

  type User {
    id: ID!
    name: String!
    role: Role!
    createdAt: Date!
  }
`;

const mocks = {
  Date: () => new Date().toISOString(),
  Role: () => faker.helpers.arrayElement(['ADMIN', 'EDITOR', 'VIEWER']),
  User: () => ({
    name: () => faker.person.fullName(),
    role: () => faker.helpers.arrayElement(['ADMIN', 'EDITOR', 'VIEWER']),
    createdAt: () => faker.date.past().toISOString(),
  }),
};

const server = new ApolloServer({
  typeDefs,
  mocks,
});

Sin mocks para escalares personalizados, Apollo devuelve null en esos campos, lo que puede romper el frontend si espera un valor válido.

Paginación con conexiones Relay

Para schemas que usan paginación por cursor estilo Relay, mockeá la estructura de conexión:

const mocks = {
  Query: () => ({
    users: () => {
      const edges = Array.from({ length: 10 }, (_, i) => ({
        node: {
          id: `user-${i}`,
          name: faker.person.fullName(),
          email: faker.internet.email(),
        },
        cursor: `cursor-${i}`,
      }));
      return {
        edges,
        pageInfo: {
          hasNextPage: true,
          hasPreviousPage: false,
          startCursor: 'cursor-0',
          endCursor: 'cursor-9',
        },
        totalCount: 100,
      };
    },
  }),
};

Esto permite testear infinite scroll, botones de “cargar más” y navegación basada en cursor sin un backend real.

Mock con MSW

Para mocking solo en el frontend sin un servidor corriendo, usá MSW con un handler GraphQL:

import { graphql } from 'msw';

export const handlers = [
  graphql.query('GetUsers', (req, res, ctx) => {
    return res(
      ctx.data({
        users: Array.from({ length: 5 }, () => ({
          id: crypto.randomUUID(),
          name: faker.person.fullName(),
          email: faker.internet.email(),
        })),
      })
    );
  }),
];

Mocks con seed para tests reproducibles

import { faker } from '@faker-js/faker';

faker.seed(12345);

const mocks = {
  User: () => ({
    name: () => faker.person.fullName(),
    email: () => faker.internet.email(),
  }),
};

Con un seed fijo, cada inicio del servidor produce los mismos datos falsos — útil para snapshot tests.

Mock de errores

Simulá respuestas de error para testear el manejo de errores en la UI:

const mocks = {
  Query: () => ({
    user: () => { throw new Error('User not found'); },
  }),
};

const server = new ApolloServer({
  typeDefs,
  mocks,
});

Buenas Prácticas

  • Usá datos realistas de faker para que las revisiones de UI detecten problemas de layout y comportamiento.
  • Empezá con auto-mocks y luego personalizá los campos uno a uno conforme el schema se estabiliza.
  • Usá preserveResolvers durante la migración para mantener resolvers reales para las partes ya construidas mientras mockeás el resto.
  • Seteá faker.seed() en tests para que los snapshots sean determinísticos.
  • Mockeá longitudes de listas que reflejen casos reales — un solo ítem no alcanza para testear paginación o estados vacíos.

Errores Comunes

  • Mockear con strings vacíos. La UI puede ocultar o colapsar valores vacíos, escondiendo bugs de layout.
  • No mockear longitudes de listas. Un mock de lista con un ítem no testea paginación o estados vacíos.
  • Olvidar deshabilitar mocks en producción. Usá variables de entorno para alternar el mocking.
  • No testear estados de error. Mockeá respuestas de error para verificar que la UI los maneja.
  • Dejar que los mocks se separen de los resolvers reales. Mantené los shapes de mock alineados con el schema productivo.

Resumen

  • Empezá con mocks: true — Apollo auto-genera valores para cada escalar. Cero código personalizado, y el endpoint funca.
  • Incorporá faker para datos realistas — strings vacíos y UUIDs random ocultan bugs de layout. Usá @faker-js/faker 8.x para nombres, emails y fechas que se vean reales.
  • Usá preserveResolvers: true durante la migración — resolvers reales para campos implementados, mocks para el resto. Intercambialos incrementalmente sin romper el frontend.
  • Alterná mocks por entorno — nunca mandes mocks a producción. Usá NODE_ENV o MOCK_API para controlar el flag.
  • Seteá faker.seed() en tests — faker.seed(12345) hace que los snapshots sean determinísticos y evita CI flaky.

Código complementario: El repo companion de graphql-mocking-apollo-server tiene ejemplos ejecutables en TypeScript para cada variante mostrada acá.

See Also

Preguntas frecuentes

¿Puedo mockear solo parte del schema?

Sí. Usá preserveResolvers: true y proveé resolvers reales para los campos implementados. Apollo mockea solo los campos sin resolvers.

¿Cómo mockeo autenticación?

Mockeá el context para devolver un usuario falso, o evitá los controles de auth en modo mock.

¿Uso Apollo mocking o MSW?

Usá Apollo mocking cuando querés un servidor corriendo. Usá MSW cuando querés interceptación del lado del cliente sin un servidor.

¿Puedo mockear subscriptions?

El mocking integrado de Apollo no soporta subscriptions. Usá un PubSub personalizado con eventos falsos para testear subscriptions.

¿Cómo mockeo scalares y enums personalizados?

Proveé funciones de mock para cada escalar y enum en el objeto mocks. Por ejemplo, Date: () => new Date().toISOString() y Role: () => faker.helpers.arrayElement(['ADMIN', 'EDITOR', 'VIEWER']). Sin esto, Apollo devuelve null en campos de scalares personalizados.

¿Cómo comparto mocks entre tests y el dev server?

Exportá el objeto mocks desde un módulo compartido e importalo tanto en la configuración de tests como en la del dev server. Eso mantiene los datos falsos consistentes entre ambientes. Usá faker.seed() en tests para output determinístico.