TL;DR
- Testing de GraphQL: Validar queries, mutaciones, subscripciones y schema en un único endpoint
- Diferencia clave con REST: HTTP 200 no significa éxito — siempre verificá el campo
errors- Tests críticos: Validación de schema, auth a nivel de campo, límites de complejidad, detección N+1
- Herramientas: Apollo MockedProvider, Jest, MSW, Insomnia, k6
- State of JS 2024: GraphQL lo usan el 44% de developers encuestados; adopción creciendo 8% anual
- Mejor práctica: Verificá cambios de schema para modificaciones breaking antes del deployment
Tiempo de lectura: 14 minutos
GraphQL se ha convertido en el lenguaje de consulta de APIs flexible dominante, cambiando fundamentalmente cómo los clientes solicitan datos de los servidores. Según la encuesta State of JavaScript 2024, el 44% de los developers encuestados usan GraphQL en sus proyectos — con una adopción creciendo aproximadamente 8% año tras año desde 2022. En GitHub, la implementación de referencia graphql-js supera las 19.000 estrellas, y Apollo Client solo tiene más de 19.000 estrellas y 10 millones de descargas semanales en npm. Testear APIs GraphQL requiere enfoques sustancialmente diferentes a REST: GraphQL usa un único endpoint para todas las operaciones, permite a los clientes especificar exactamente qué datos necesitan, y puede devolver HTTP 200 con errores embebidos en el cuerpo de la respuesta. Esta guía cubre el panorama completo del testing de GraphQL: desde tests básicos de queries hasta validación de schema, efectos secundarios de mutaciones, subscripciones en tiempo real y testing de performance con análisis de complejidad de queries.
Fundamentos del Testing de GraphQL
El testing de GraphQL difiere del testing de APIs REST en varios aspectos clave. A diferencia de REST, donde cada endpoint devuelve una estructura de datos fija, GraphQL permite a los clientes solicitar exactamente los datos que necesitan a través de un único endpoint. Esta flexibilidad crea desafíos únicos de testing.
Diferencias clave con el Testing REST
| Aspecto | Testing REST | Testing GraphQL |
|---|---|---|
| Endpoints | Múltiples endpoints | Endpoint único |
| Estructura de Datos | Fija por endpoint | Dinámica por query |
| Over-fetching | Problema común | Eliminado por diseño |
| Under-fetching | Requiere múltiples peticiones | Resuelto en una sola query |
| Versionado | Versiones basadas en URL | Evolución del schema |
| Manejo de Errores | Códigos de estado HTTP | Éxito parcial posible |
La naturaleza de endpoint único de GraphQL significa que las pruebas tradicionales de códigos de estado HTTP son menos relevantes. Una petición GraphQL puede devolver HTTP 200 incluso con errores en los datos de respuesta.
“GraphQL rompe la suposición de que HTTP 200 significa éxito. Cada test de GraphQL debe inspeccionar el campo
errorsen el cuerpo de la respuesta — no solo el código de estado. Los equipos que omiten esto terminan con suites de tests que dan falsa confianza: todo en verde, pero errores reales sin detectar.” — Yuri Kan, Senior QA Lead
Estrategias de Testing de Queries
El testing de queries forma la base del testing de GraphQL. Las queries permiten a los clientes obtener datos, y probarlas asegura la recuperación correcta de datos y la resolución adecuada de campos.
Testing Básico de Queries
Comienza con pruebas simples de queries que verifican funcionalidad básica:
// Ejemplo usando Jest y Apollo Client
import { ApolloClient, InMemoryCache, gql } from '@apollo/client';
describe('Pruebas de Query de Usuario', () => {
let client;
beforeAll(() => {
client = new ApolloClient({
uri: 'http://localhost:4000/graphql',
cache: new InMemoryCache()
});
});
test('debe obtener usuario por ID', async () => {
const GET_USER = gql`
query GetUser($id: ID!) {
user(id: $id) {
id
name
email
}
}
`;
const { data } = await client.query({
query: GET_USER,
variables: { id: '123' }
});
expect(data.user).toBeDefined();
expect(data.user.id).toBe('123');
expect(data.user.name).toBeTruthy();
expect(data.user.email).toMatch(/^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/);
});
});
Testing de Queries Anidadas
El poder de GraphQL radica en las queries anidadas. Prueba el anidamiento profundo de queries para asegurar que los resolvers funcionen correctamente:
test('debe obtener usuario con posts y comentarios anidados', async () => {
const GET_USER_WITH_POSTS = gql`
query GetUserWithPosts($id: ID!) {
user(id: $id) {
id
name
posts {
id
title
comments {
id
content
author {
name
}
}
}
}
}
`;
const { data } = await client.query({
query: GET_USER_WITH_POSTS,
variables: { id: '123' }
});
expect(data.user.posts).toBeInstanceOf(Array);
expect(data.user.posts[0].comments).toBeInstanceOf(Array);
expect(data.user.posts[0].comments[0].author.name).toBeDefined();
});
Testing de Rendimiento de Queries
Prueba el rendimiento de queries, especialmente para queries anidadas complejas que podrían desencadenar el problema N+1:
test('debe resolver datos anidados eficientemente sin queries N+1', async () => {
const startTime = Date.now();
const { data } = await client.query({
query: GET_USER_WITH_POSTS,
variables: { id: '123' }
});
const duration = Date.now() - startTime;
// Debe completarse en menos de 500ms incluso con datos anidados
expect(duration).toBeLessThan(500);
expect(data.user.posts.length).toBeGreaterThan(0);
});
Testing de Mutaciones
Las mutaciones modifican datos del lado del servidor. Probar mutaciones requiere verificar tanto la respuesta inmediata como los efectos secundarios.
Testing de Mutaciones de Creación
test('debe crear nuevo usuario', async () => {
const CREATE_USER = gql`
mutation CreateUser($input: UserInput!) {
createUser(input: $input) {
id
name
email
createdAt
}
}
`;
const { data } = await client.mutate({
mutation: CREATE_USER,
variables: {
input: {
name: 'Juan Pérez',
email: 'juan@ejemplo.com',
password: 'ClaveSegura123!'
}
}
});
expect(data.createUser.id).toBeDefined();
expect(data.createUser.name).toBe('Juan Pérez');
expect(data.createUser.email).toBe('juan@ejemplo.com');
expect(new Date(data.createUser.createdAt)).toBeInstanceOf(Date);
});
Testing de Mutaciones de Actualización
Verifica que las actualizaciones funcionen correctamente y devuelvan datos actualizados:
test('debe actualizar perfil de usuario', async () => {
const UPDATE_USER = gql`
mutation UpdateUser($id: ID!, $input: UserUpdateInput!) {
updateUser(id: $id, input: $input) {
id
name
bio
updatedAt
}
}
`;
const { data } = await client.mutate({
mutation: UPDATE_USER,
variables: {
id: '123',
input: {
name: 'Juana Pérez',
bio: 'Texto de biografía actualizado'
}
}
});
expect(data.updateUser.name).toBe('Juana Pérez');
expect(data.updateUser.bio).toBe('Texto de biografía actualizado');
});
Testing de Mutaciones de Eliminación
Prueba mutaciones de eliminación y verifica la limpieza adecuada:
test('debe eliminar usuario y datos relacionados en cascada', async () => {
const DELETE_USER = gql`
mutation DeleteUser($id: ID!) {
deleteUser(id: $id) {
success
message
}
}
`;
const { data } = await client.mutate({
mutation: DELETE_USER,
variables: { id: '123' }
});
expect(data.deleteUser.success).toBe(true);
// Verificar que el usuario ya no existe
const GET_USER = gql`
query GetUser($id: ID!) {
user(id: $id) {
id
}
}
`;
await expect(
client.query({ query: GET_USER, variables: { id: '123' } })
).rejects.toThrow();
});
Testing de Subscripciones
Las subscripciones de GraphQL permiten actualizaciones de datos en tiempo real vía WebSockets. Probar subscripciones requiere técnicas diferentes.
Testing de Subscripciones WebSocket
import { WebSocketLink } from '@apollo/client/link/ws';
import { SubscriptionClient } from 'subscriptions-transport-ws';
import ws from 'ws';
test('debe recibir actualizaciones en tiempo real vía subscripción', (done) => {
const wsClient = new SubscriptionClient(
'ws://localhost:4000/graphql',
{ reconnect: true },
ws
);
const NEW_MESSAGE_SUBSCRIPTION = gql`
subscription OnNewMessage($channelId: ID!) {
messageAdded(channelId: $channelId) {
id
content
author {
name
}
createdAt
}
}
`;
const observable = wsClient.request({
query: NEW_MESSAGE_SUBSCRIPTION,
variables: { channelId: 'channel-1' }
});
const subscription = observable.subscribe({
next: (result) => {
expect(result.data.messageAdded).toBeDefined();
expect(result.data.messageAdded.content).toBeTruthy();
subscription.unsubscribe();
wsClient.close();
done();
},
error: (error) => {
done(error);
}
});
// Disparar mutación para generar evento de subscripción
setTimeout(() => {
client.mutate({
mutation: gql`
mutation {
sendMessage(channelId: "channel-1", content: "Mensaje de prueba") {
id
}
}
`
});
}, 100);
}, 10000);
Testing de Validación de Schema
La validación de schema asegura que tu API GraphQL mantenga definiciones de tipo consistentes y siga mejores prácticas.
Linting de Schema
Usa GraphQL Inspector o herramientas similares para validar cambios de schema:
import { buildSchema } from 'graphql';
import { findBreakingChanges, findDangerousChanges } from 'graphql';
test('no debe introducir cambios de schema que rompan compatibilidad', () => {
const oldSchema = buildSchema(oldSchemaSDL);
const newSchema = buildSchema(newSchemaSDL);
const breakingChanges = findBreakingChanges(oldSchema, newSchema);
const dangerousChanges = findDangerousChanges(oldSchema, newSchema);
expect(breakingChanges).toHaveLength(0);
// Registrar cambios peligrosos para revisión
if (dangerousChanges.length > 0) {
console.warn('Cambios peligrosos de schema detectados:', dangerousChanges);
}
});
Testing de Cobertura de Tipos
Asegura que todos los tipos tengan descripciones adecuadas y campos requeridos:
test('debe tener descripciones para todos los tipos y campos', () => {
const schema = buildSchema(schemaSDL);
const typeMap = schema.getTypeMap();
Object.keys(typeMap).forEach(typeName => {
// Saltar tipos integrados
if (typeName.startsWith('__')) return;
const type = typeMap[typeName];
if (type.description === undefined) {
throw new Error(`Tipo ${typeName} sin descripción`);
}
// Verificar descripciones de campos para tipos de objeto
if ('getFields' in type) {
const fields = type.getFields();
Object.keys(fields).forEach(fieldName => {
if (!fields[fieldName].description) {
throw new Error(`Campo ${typeName}.${fieldName} sin descripción`);
}
});
}
});
});
Mocking de Respuestas GraphQL
El mocking es esencial para probar aplicaciones cliente sin depender de un servidor en vivo.
Mocking con Apollo Client
import { MockedProvider } from '@apollo/client/testing';
const mocks = [
{
request: {
query: GET_USER,
variables: { id: '123' }
},
result: {
data: {
user: {
id: '123',
name: 'Juan Pérez',
email: 'juan@ejemplo.com'
}
}
}
}
];
test('debe renderizar datos de usuario desde query mockeada', async () => {
const { getByText } = render(
<MockedProvider mocks={mocks} addTypename={false}>
<UserProfile userId="123" />
</MockedProvider>
);
// Esperar a que los datos se carguen
await waitFor(() => {
expect(getByText('Juan Pérez')).toBeInTheDocument();
});
});
Mock Service Worker (MSW) para GraphQL
import { graphql } from 'msw';
import { setupServer } from 'msw/node';
const server = setupServer(
graphql.query('GetUser', (req, res, ctx) => {
const { id } = req.variables;
return res(
ctx.data({
user: {
id,
name: 'Usuario Simulado',
email: 'simulado@ejemplo.com'
}
})
);
}),
graphql.mutation('CreateUser', (req, res, ctx) => {
const { input } = req.variables;
return res(
ctx.data({
createUser: {
id: 'nuevo-id',
...input,
createdAt: new Date().toISOString()
}
})
);
})
);
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
Testing de Manejo de Errores
GraphQL tiene características únicas de manejo de errores. Los errores pueden ocurrir en múltiples niveles.
Testing de Errores a Nivel de Campo
test('debe manejar errores a nivel de campo graciosamente', async () => {
const { data, errors } = await client.query({
query: gql`
query {
user(id: "123") {
id
name
restrictedField
}
}
`
});
expect(errors).toBeDefined();
expect(errors[0].path).toContain('restrictedField');
expect(data.user.id).toBe('123');
expect(data.user.name).toBeDefined();
expect(data.user.restrictedField).toBeNull();
});
Testing de Errores de Red
test('debe manejar errores de red', async () => {
const errorLink = onError(({ networkError, graphQLErrors }) => {
if (networkError) {
console.log(`[Error de red]: ${networkError}`);
}
});
const clientWithErrorHandling = new ApolloClient({
link: from([errorLink, httpLink]),
cache: new InMemoryCache()
});
await expect(
clientWithErrorHandling.query({
query: GET_USER,
variables: { id: 'invalid' }
})
).rejects.toThrow();
});
Testing de Integración con Bases de Datos
Prueba resolvers de GraphQL con operaciones reales de base de datos:
import { MongoMemoryServer } from 'mongodb-memory-server';
describe('Integración GraphQL con Base de Datos', () => {
let mongoServer;
let db;
beforeAll(async () => {
mongoServer = await MongoMemoryServer.create();
const uri = mongoServer.getUri();
// Conectar a MongoDB en memoria
});
afterAll(async () => {
await mongoServer.stop();
});
test('debe persistir datos de usuario', async () => {
const { data } = await client.mutate({
mutation: CREATE_USER,
variables: { input: { name: 'Usuario Prueba', email: 'prueba@ejemplo.com' } }
});
// Verificar en base de datos
const dbUser = await db.collection('users').findOne({ _id: data.createUser.id });
expect(dbUser.name).toBe('Usuario Prueba');
});
});
Mejores Prácticas y Recomendaciones
Checklist de Testing
- ✅ Probar todas las variaciones de query con diferentes combinaciones de argumentos
- ✅ Verificar efectos secundarios de mutaciones y persistencia de datos
- ✅ Probar comportamiento en tiempo real de subscripciones y limpieza
- ✅ Validar cambios de schema para modificaciones que rompan compatibilidad
- ✅ Mockear dependencias externas de manera consistente
- ✅ Probar escenarios de error a nivel de campo y petición
- ✅ Verificar reglas de autorización y autenticación
- ✅ Probar paginación y patrones de conexión
- ✅ Validar sanitización y validación de entrada
- ✅ Monitorear rendimiento y complejidad de queries
Tips de Testing de Rendimiento
Usa análisis de complejidad de queries para prevenir queries costosas:
import { createComplexityLimitRule } from 'graphql-validation-complexity';
const schema = makeExecutableSchema({
typeDefs,
resolvers
});
const complexityLimit = createComplexityLimitRule(1000);
test('debe rechazar queries excesivamente complejas', () => {
const complexQuery = gql`...`; // Query muy anidada
const errors = validate(schema, parse(complexQuery), [complexityLimit]);
expect(errors.length).toBeGreaterThan(0);
expect(errors[0].message).toContain('excede la complejidad máxima');
});
Conclusión
El testing de GraphQL requiere entender las características únicas del protocolo: endpoints únicos, queries flexibles, subscripciones en tiempo real y errores parciales. Al implementar estrategias comprensivas de testing para queries, mutaciones, subscripciones, validación de schema y manejo de errores, puedes asegurar que tu API GraphQL sea confiable y eficiente.
Enfócate en probar la lógica de negocio dentro de los resolvers, validar la evolución del schema y asegurar el manejo apropiado de errores en todos los niveles. Usa mocking extensivamente para pruebas del lado del cliente, y testing de integración para flujos de datos críticos. Con estas prácticas implementadas, tu API GraphQL será robusta y mantenible.
FAQ
¿Qué es el testing de GraphQL?
El testing de GraphQL es el proceso de validar que una API GraphQL maneja correctamente queries, mutaciones y subscripciones. Difiere del testing REST porque GraphQL usa un único endpoint, devuelve estructuras de datos dinámicas y puede devolver HTTP 200 con errores en el cuerpo de la respuesta.
¿Cómo difiere el testing de GraphQL del testing de APIs REST?
El testing REST valida endpoints fijos con códigos HTTP. El testing GraphQL valida un único endpoint donde la forma de la respuesta la determina el query. GraphQL puede devolver HTTP 200 con errores — debés inspeccionar el campo errors en el cuerpo de la respuesta, no solo el código de estado.
¿Qué herramientas se usan para testing de GraphQL?
Herramientas populares: Apollo Client con MockedProvider para testing de frontend, Jest con graphql-tag para unit tests, Insomnia o GraphQL Playground para exploración manual, MSW para integration tests, y k6 con requests GraphQL personalizadas para performance testing.
¿Qué debo testear en una API GraphQL?
Testea todas las variaciones de queries, efectos secundarios de mutaciones y persistencia de datos, comportamiento en tiempo real de subscripciones, cambios de schema para modificaciones breaking, reglas de autorización (permisos a nivel de campo), manejo de errores a nivel de campo y request, y límites de complejidad de queries.
Fuentes y Lectura Adicional
- GraphQL Official Documentation — especificación completa del lenguaje de consulta GraphQL
- Apollo Client Documentation — guías de integración de cliente GraphQL y patrones de testing
Ver También
- API Testing Mastery: Complete Guide - Fundamentos completos de testing de APIs
- REST, GraphQL, and gRPC: Choosing the Right Protocol for Mobile Apps - Comparación de protocolos para elegir la mejor opción
- gRPC Testing: Comprehensive Guide for RPC API Testing - Testing especializado para APIs gRPC
- API Performance Testing: Tools and Best Practices - Herramientas y metodologías para testing de rendimiento
- Postman: From Manual to Automation - Automatización de pruebas de API con Postman
