DEV Community

Cover image for Complete Guide to Testing GraphQL Mutations: Best Practices and Code Examples
Aarav Joshi
Aarav Joshi

Posted on

Complete Guide to Testing GraphQL Mutations: Best Practices and Code Examples

As a best-selling author, I invite you to explore my books on Amazon. Don't forget to follow me on Medium and show your support. Thank you! Your support means the world!

Testing GraphQL mutations effectively is crucial for building reliable APIs. Let's explore essential practices for thorough mutation testing.

Input Validation Testing

Testing input validation ensures your mutations maintain data integrity. Start with basic field validations:

const createUserMutation = `
  mutation($input: CreateUserInput!) {
    createUser(input: $input) {
      id
      email
      name
    }
  }
`;

test('validates required fields', async () => {
  const result = await executeGraphQL({
    query: createUserMutation,
    variables: {
      input: { email: '', name: '' }
    }
  });

  expect(result.errors).toHaveLength(2);
  expect(result.errors[0].message).toContain('email is required');
});
Enter fullscreen mode Exit fullscreen mode

Test complex validation rules:

test('validates password requirements', async () => {
  const result = await executeGraphQL({
    query: createUserMutation,
    variables: {
      input: {
        email: 'test@example.com',
        password: '123' // Too short
      }
    }
  });

  expect(result.errors[0].message).toContain('minimum 8 characters');
});
Enter fullscreen mode Exit fullscreen mode

Authorization Testing

Verify access control with different user roles:

test('admin can delete users', async () => {
  const context = { user: adminUser };

  const result = await executeGraphQL({
    query: deleteUserMutation,
    variables: { id: userId },
    context
  });

  expect(result.data.deleteUser).toBeTruthy();
});

test('regular users cannot delete others', async () => {
  const context = { user: regularUser };

  const result = await executeGraphQL({
    query: deleteUserMutation,
    variables: { id: otherUserId },
    context
  });

  expect(result.errors[0].message).toContain('unauthorized');
});
Enter fullscreen mode Exit fullscreen mode

Error Handling

Test various error scenarios:

test('handles duplicate email gracefully', async () => {
  // First creation
  await executeGraphQL({
    query: createUserMutation,
    variables: {
      input: { email: 'test@example.com' }
    }
  });

  // Duplicate attempt
  const result = await executeGraphQL({
    query: createUserMutation,
    variables: {
      input: { email: 'test@example.com' }
    }
  });

  expect(result.errors[0].message).toContain('email already exists');
  expect(result.errors[0].extensions.code).toBe('DUPLICATE_EMAIL');
});
Enter fullscreen mode Exit fullscreen mode

Side Effects

Test related data updates:

test('deleting user removes associated posts', async () => {
  const result = await executeGraphQL({
    query: deleteUserMutation,
    variables: { id: userWithPosts.id }
  });

  const postsResult = await executeGraphQL({
    query: getUserPosts,
    variables: { userId: userWithPosts.id }
  });

  expect(postsResult.data.posts).toHaveLength(0);
});
Enter fullscreen mode Exit fullscreen mode

Optimistic Updates

Test client-side state management:

test('handles optimistic update rollback', async () => {
  const cache = new InMemoryCache();

  // Simulate network error
  mockNetworkError(createPostMutation);

  const client = new ApolloClient({
    cache,
    link: errorLink
  });

  try {
    await client.mutate({
      mutation: createPostMutation,
      optimisticResponse: {
        createPost: {
          id: 'temp-id',
          title: 'New Post'
        }
      }
    });
  } catch (error) {
    const cachedData = cache.readQuery({ query: postsQuery });
    expect(cachedData.posts).not.toContainEqual({
      id: 'temp-id',
      title: 'New Post'
    });
  }
});
Enter fullscreen mode Exit fullscreen mode

Performance Testing

Measure mutation execution time:

test('completes batch update within timeout', async () => {
  const startTime = Date.now();

  const result = await executeGraphQL({
    query: updateMultipleUsersMutation,
    variables: {
      inputs: generateManyUserUpdates(100)
    }
  });

  const duration = Date.now() - startTime;
  expect(duration).toBeLessThan(1000);
  expect(result.errors).toBeUndefined();
});
Enter fullscreen mode Exit fullscreen mode

Integration Testing

Test mutations with real database operations:

test('creates user with nested relationships', async () => {
  const result = await executeGraphQL({
    query: createUserWithProfileMutation,
    variables: {
      input: {
        email: 'test@example.com',
        profile: {
          bio: 'Test bio',
          social: {
            twitter: '@test'
          }
        }
      }
    }
  });

  const user = await db.user.findUnique({
    where: { id: result.data.createUser.id },
    include: { profile: true }
  });

  expect(user.profile.bio).toBe('Test bio');
  expect(user.profile.social.twitter).toBe('@test');
});
Enter fullscreen mode Exit fullscreen mode

Subscription Testing

Test real-time updates triggered by mutations:

test('notifies subscribers of user updates', async () => {
  const subscription = executeGraphQL({
    query: userUpdatedSubscription,
    variables: { userId }
  });

  const updates = [];
  subscription.subscribe(result => {
    updates.push(result.data.userUpdated);
  });

  await executeGraphQL({
    query: updateUserMutation,
    variables: {
      input: { id: userId, name: 'Updated Name' }
    }
  });

  await waitFor(() => {
    expect(updates).toHaveLength(1);
    expect(updates[0].name).toBe('Updated Name');
  });
});
Enter fullscreen mode Exit fullscreen mode

File Upload Testing

Test mutations handling file uploads:

test('processes avatar upload', async () => {
  const file = new File(['test'], 'avatar.jpg', {
    type: 'image/jpeg'
  });

  const result = await executeGraphQL({
    query: uploadAvatarMutation,
    variables: {
      file,
      userId
    }
  });

  expect(result.data.uploadAvatar.avatarUrl).toMatch(/^https:\/\//);

  const uploadedFile = await storage.get(result.data.uploadAvatar.avatarUrl);
  expect(uploadedFile).toBeDefined();
});
Enter fullscreen mode Exit fullscreen mode

These testing practices ensure robust GraphQL mutations. Always test edge cases, error scenarios, and maintain comprehensive test coverage. Regular testing during development catches issues early and maintains API reliability.

Remember to use testing utilities specific to your GraphQL implementation and test runner. Keep tests focused, maintainable, and meaningful for your application's requirements.

Isolate tests properly and clean up test data after each run. This prevents test interference and maintains consistent results. Use meaningful assertions that verify both successful operations and proper error handling.


101 Books

101 Books is an AI-driven publishing company co-founded by author Aarav Joshi. By leveraging advanced AI technology, we keep our publishing costs incredibly low—some books are priced as low as $4—making quality knowledge accessible to everyone.

Check out our book Golang Clean Code available on Amazon.

Stay tuned for updates and exciting news. When shopping for books, search for Aarav Joshi to find more of our titles. Use the provided link to enjoy special discounts!

Our Creations

Be sure to check out our creations:

Investor Central | Investor Central Spanish | Investor Central German | Smart Living | Epochs & Echoes | Puzzling Mysteries | Hindutva | Elite Dev | JS Schools


We are on Medium

Tech Koala Insights | Epochs & Echoes World | Investor Central Medium | Puzzling Mysteries Medium | Science & Epochs Medium | Modern Hindutva

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more

Top comments (0)

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay