How to Test GraphQL in React
Testing GraphQL in React is essential for ensuring your application fetches, mutates, and displays data correctly. This article provides a straightforward guide on how to test GraphQL queries and mutations in React components using Jest, React Testing Library, and Apollo Client’s testing utilities. You will learn how to mock GraphQL network requests, verify loading and error states, and assert that the correct data renders on the screen.
The Testing Toolset
To test GraphQL in a React application, you need three primary tools:
1. Jest: The test runner and assertion library. 2.
React Testing Library (RTL): For rendering components
and interacting with the DOM. 3. MockedProvider: A
utility provided by @apollo/client/testing that mocks
GraphQL network traffic so you do not have to make real API calls during
tests.
Step 1: Create the Component to Test
Consider a simple component named UserCharacter that
fetches a character’s name from a GraphQL API using the
useQuery hook.
import React from 'react';
import { useQuery, gql } from '@apollo/client';
export const GET_CHARACTER = gql`
query GetCharacter($id: ID!) {
character(id: $id) {
id
name
}
}
`;
export function UserCharacter({ id }) {
const { loading, error, data } = useQuery(GET_CHARACTER, {
variables: { id },
});
if (loading) return <p>Loading...</p>;
if (error) return <p>Error loading character.</p>;
return <h1>{data.character.name}</h1>;
}Step 2: Define the GraphQL Mocks
To test this component, you must mock the network response. A mock in
Apollo Client is an object containing a request (the query
and variables) and a result (the simulated data returned by
the API).
import { GET_CHARACTER } from './UserCharacter';
const mocks = [
{
request: {
query: GET_CHARACTER,
variables: { id: '1' },
},
result: {
data: {
character: { id: '1', name: 'Luke Skywalker' },
},
},
},
];Step 3: Write the Tests
When testing components with MockedProvider, you must
wrap your component with it and pass the mocks array as a
prop. Because GraphQL queries are asynchronous, you will need to use
asynchronous utilities from React Testing Library to wait for the data
to render.
Here is the complete test file:
import React from 'react';
import { render, screen } from '@testing-library/react';
import { MockedProvider } from '@apollo/client/testing';
import { UserCharacter, GET_CHARACTER } from './UserCharacter';
const mocks = [
{
request: {
query: GET_CHARACTER,
variables: { id: '1' },
},
result: {
data: {
character: { id: '1', name: 'Luke Skywalker' },
},
},
},
];
describe('UserCharacter Component', () => {
it('should render loading state initially', () => {
render(
<MockedProvider mocks={mocks} addTypename={false}>
<UserCharacter id="1" />
</MockedProvider>
);
expect(screen.getByText('Loading...')).toBeInTheDocument();
});
it('should render the character name after loading', async () => {
render(
<MockedProvider mocks={mocks} addTypename={false}>
<UserCharacter id="1" />
</MockedProvider>
);
// Use findByText to wait for the asynchronous update
const characterName = await screen.findByText('Luke Skywalker');
expect(characterName).toBeInTheDocument();
});
it('should render an error state when the query fails', async () => {
const errorMock = [
{
request: {
query: GET_CHARACTER,
variables: { id: '1' },
},
error: new Error('An error occurred'),
},
];
render(
<MockedProvider mocks={errorMock} addTypename={false}>
<UserCharacter id="1" />
</MockedProvider>
);
const errorElement = await screen.findByText('Error loading character.');
expect(errorElement).toBeInTheDocument();
});
});Key Best Practices
- Match variables exactly: The variables object in
your mock must exactly match the variables passed by the component. If
they do not match, the
MockedProviderwill throw an error stating that no mock was found for the query. - Turn off Typenames if not needed: Passing
addTypename={false}toMockedProviderprevents you from having to write__typenamefields inside your mock data objects. If your production application relies on__typenamefor caching, set this totrueand include the fields in your mocks. - Use
findByqueries: Because GraphQL queries resolve asynchronously, always use React Testing Library’sfindBy*queries (likefindByText) to wait for the component to transition from the loading state to the success or error state.