Mock implementations for testing GraphQL operations and React components. Apollo Client provides comprehensive testing support through mock links, providers, and utilities that enable reliable testing of GraphQL applications.
Mock transport link for testing GraphQL operations without network requests.
/**
* Mock link for testing GraphQL operations
* Provides predefined responses for specific queries, mutations, and subscriptions
*/
class MockLink extends ApolloLink {
constructor(mockedResponses: MockedResponse[], addTypename?: boolean, options?: MockLinkOptions);
/**
* Add additional mocked response
* @param mockedResponse - Response to add to mock queue
*/
addMockedResponse(mockedResponse: MockedResponse): void;
}
interface MockedResponse<TData = Record<string, any>, TVariables = Record<string, any>> {
/** GraphQL request to match */
request: {
query: DocumentNode;
variables?: TVariables;
operationName?: string;
};
/** Static response data */
result?: {
data?: TData;
errors?: readonly GraphQLError[];
extensions?: any;
};
/** Dynamic response function */
resultFunction?: () => FetchResult<TData>;
/** Error to throw */
error?: Error;
/** Response delay in milliseconds */
delay?: number;
/** How many times this response can be used */
maxUsageCount?: number;
}
interface MockLinkOptions {
/** Show warnings for unused mocks */
showWarnings?: boolean;
/** Callback when request has no matching mock */
onError?: (error: Error, operation: Operation) => void;
}Usage Example:
import { MockLink } from "@apollo/client/testing";
const GET_USER = gql`
query GetUser($id: ID!) {
user(id: $id) {
id
name
email
}
}
`;
const CREATE_USER = gql`
mutation CreateUser($input: CreateUserInput!) {
createUser(input: $input) {
id
name
email
}
}
`;
const mocks = [
{
request: {
query: GET_USER,
variables: { id: '1' }
},
result: {
data: {
user: {
id: '1',
name: 'John Doe',
email: 'john@example.com',
__typename: 'User'
}
}
}
},
{
request: {
query: CREATE_USER,
variables: {
input: { name: 'Jane Doe', email: 'jane@example.com' }
}
},
result: {
data: {
createUser: {
id: '2',
name: 'Jane Doe',
email: 'jane@example.com',
__typename: 'User'
}
}
},
delay: 500 // Simulate network delay
}
];
const mockLink = new MockLink(mocks, true);React provider component for testing Apollo Client components with mocked responses.
/**
* React provider for testing components with mocked GraphQL responses
* Wraps components with Apollo Client configured for testing
*/
interface MockedProviderProps<TSerializedCache = {}> {
/** Mock responses for GraphQL operations */
mocks?: MockedResponse[];
/** Add __typename to all objects */
addTypename?: boolean;
/** Default options for operations */
defaultOptions?: DefaultOptions;
/** Cache instance (defaults to InMemoryCache) */
cache?: ApolloCache<TSerializedCache>;
/** Local state resolvers */
resolvers?: Resolvers;
/** Type definitions */
typeDefs?: string | string[] | DocumentNode | DocumentNode[];
/** Additional props passed to children */
childProps?: object;
/** Child components */
children?: React.ReactNode;
/** Custom link (replaces MockLink) */
link?: ApolloLink;
/** Show warnings for unused mocks */
showWarnings?: boolean;
}
interface MockedProvider<TSerializedCache = {}> extends React.ComponentType<MockedProviderProps<TSerializedCache>> {}Usage Example:
import { render, screen, waitFor } from '@testing-library/react';
import { MockedProvider } from '@apollo/client/testing';
import UserProfile from './UserProfile';
const GET_USER_PROFILE = gql`
query GetUserProfile($id: ID!) {
user(id: $id) {
id
name
email
bio
}
}
`;
const mocks = [
{
request: {
query: GET_USER_PROFILE,
variables: { id: '1' }
},
result: {
data: {
user: {
id: '1',
name: 'John Doe',
email: 'john@example.com',
bio: 'Software developer',
__typename: 'User'
}
}
}
}
];
test('renders user profile', async () => {
render(
<MockedProvider mocks={mocks} addTypename={false}>
<UserProfile userId="1" />
</MockedProvider>
);
// Initially shows loading
expect(screen.getByText('Loading...')).toBeInTheDocument();
// Wait for data to load
await waitFor(() => {
expect(screen.getByText('John Doe')).toBeInTheDocument();
expect(screen.getByText('john@example.com')).toBeInTheDocument();
expect(screen.getByText('Software developer')).toBeInTheDocument();
});
});
test('handles mutation', async () => {
const UPDATE_USER = gql`
mutation UpdateUser($id: ID!, $input: UpdateUserInput!) {
updateUser(id: $id, input: $input) {
id
name
email
}
}
`;
const mutationMocks = [
...mocks,
{
request: {
query: UPDATE_USER,
variables: {
id: '1',
input: { name: 'John Updated' }
}
},
result: {
data: {
updateUser: {
id: '1',
name: 'John Updated',
email: 'john@example.com',
__typename: 'User'
}
}
}
}
];
render(
<MockedProvider mocks={mutationMocks}>
<EditUserForm userId="1" />
</MockedProvider>
);
// Test mutation interaction
const saveButton = await screen.findByText('Save');
fireEvent.click(saveButton);
await waitFor(() => {
expect(screen.getByText('User updated successfully')).toBeInTheDocument();
});
});Additional utilities for testing GraphQL operations and cache behavior.
/**
* Add realistic network delay to mock responses
* @param min - Minimum delay in milliseconds
* @param max - Maximum delay in milliseconds
* @returns Random delay between min and max
*/
function realisticDelay(min: number = 200, max: number = 800): number;
/**
* Mock subscription link for testing real-time features
*/
class MockSubscriptionLink extends ApolloLink {
constructor();
/**
* Simulate subscription data
* @param result - Subscription result to emit
*/
simulateResult(result: FetchResult): void;
/**
* Simulate subscription completion
*/
simulateComplete(): void;
/**
* Simulate subscription error
* @param error - Error to emit
*/
simulateError(error: Error): void;
}Usage Example:
import { MockSubscriptionLink, realisticDelay } from '@apollo/client/testing';
// Realistic delay testing
const mocksWithDelay = [
{
request: { query: GET_USERS },
result: { data: { users: [] } },
delay: realisticDelay(100, 500)
}
];
// Subscription testing
const COMMENT_SUBSCRIPTION = gql`
subscription CommentAdded($postId: ID!) {
commentAdded(postId: $postId) {
id
content
author { name }
}
}
`;
test('handles subscription updates', async () => {
const subscriptionLink = new MockSubscriptionLink();
const client = new ApolloClient({
link: subscriptionLink,
cache: new InMemoryCache()
});
render(
<ApolloProvider client={client}>
<CommentList postId="1" />
</ApolloProvider>
);
// Simulate subscription data
act(() => {
subscriptionLink.simulateResult({
data: {
commentAdded: {
id: 'comment-1',
content: 'Great post!',
author: { name: 'Alice', __typename: 'User' },
__typename: 'Comment'
}
}
});
});
await waitFor(() => {
expect(screen.getByText('Great post!')).toBeInTheDocument();
expect(screen.getByText('Alice')).toBeInTheDocument();
});
});Testing error scenarios and error handling behavior.
// Network error testing
const networkErrorMock = {
request: { query: GET_USER, variables: { id: '1' } },
error: new Error('Network error occurred')
};
// GraphQL error testing
const graphqlErrorMock = {
request: { query: GET_USER, variables: { id: '1' } },
result: {
errors: [
{
message: 'User not found',
locations: [{ line: 2, column: 3 }],
path: ['user']
}
]
}
};
// Custom error response
const customErrorMock = {
request: { query: GET_USER, variables: { id: '1' } },
resultFunction: () => {
throw new Error('Custom error from result function');
}
};Testing cache behavior and modifications.
test('cache updates correctly after mutation', async () => {
const cache = new InMemoryCache();
// Pre-populate cache
cache.writeQuery({
query: GET_USERS,
data: {
users: [
{ id: '1', name: 'John', __typename: 'User' }
]
}
});
const mocks = [
{
request: {
query: CREATE_USER,
variables: { input: { name: 'Jane' } }
},
result: {
data: {
createUser: {
id: '2',
name: 'Jane',
__typename: 'User'
}
}
}
}
];
render(
<MockedProvider mocks={mocks} cache={cache}>
<CreateUserForm />
</MockedProvider>
);
// Trigger mutation
const createButton = screen.getByText('Create User');
fireEvent.click(createButton);
await waitFor(() => {
// Verify cache was updated
const cachedData = cache.readQuery({ query: GET_USERS });
expect(cachedData.users).toHaveLength(2);
expect(cachedData.users[1].name).toBe('Jane');
});
});interface MockLinkOptions {
showWarnings?: boolean;
onError?: (error: Error, operation: Operation) => void;
}
type ResultFunction<TData> = () => FetchResult<TData> | Promise<FetchResult<TData>>;
interface MockedRequest<TVariables = Record<string, any>> {
query: DocumentNode;
variables?: TVariables;
operationName?: string;
context?: Record<string, any>;
}
interface MockSubscriptionLinkOptions {
onSubscribe?: (operation: Operation, handler: (result: FetchResult) => void) => void;
}