Q&A
How to test your Apollo Links
Overview
An Apollo Link is a function that takes a GraphQL operation and a "forward" function (the next link in the chain) and returns an Observable. Testing a link means verifying that it correctly transforms the operation, the context, or the response - without needing a real server. The key is constructing a minimal, deterministic chain where you control the "end" of the pipe.
graph LR
A[Your Link under test] --> B[Mock Terminating Link]
C["execute(chain, operation)"] --> A
B --> D["Observable.of(mockResponse)"]
D --> E[Subscribe and assert]
style A fill:#1e1b4b,color:#fff
Setting Up: Minimal Test Chain
A link chain needs a terminating link - one that doesn't call forward() but instead returns data. ApolloLink.from assembles the chain:
import { ApolloLink, execute, Observable } from "@apollo/client";
import { gql } from "@apollo/client/core";
// Create a mock terminating link that returns a fixed response:
const mockTerminator = new ApolloLink(() =>
Observable.of({ data: { user: { id: "1", name: "Ada" } } })
);
// Your link under test (e.g. an auth header link):
const authLink = new ApolloLink((operation, forward) => {
operation.setContext(({ headers = {} }) => ({
headers: { ...headers, authorization: "Bearer test-token" },
}));
return forward(operation);
});
// Chain: authLink → mockTerminator
const chain = ApolloLink.from([authLink, mockTerminator]);
Running the Chain and Asserting
Use execute() to run an operation through the chain and subscribe to the Observable result:
const op = { query: gql`{ user { id name } }` };
const result = await new Promise((resolve, reject) => {
execute(chain, op).subscribe({
next: resolve,
error: reject,
});
});
expect(result).toEqual({ data: { user: { id: "1", name: "Ada" } } });
Testing What Your Link Does to the Request
Capture what the link passed to forward():
let capturedOperation: any;
const capturingTerminator = new ApolloLink((op) => {
capturedOperation = op;
return Observable.of({ data: {} });
});
const chain = ApolloLink.from([authLink, capturingTerminator]);
execute(chain, { query: gql`{ me { id } }` }).subscribe(() => {
const headers = capturedOperation.getContext().headers;
expect(headers.authorization).toBe("Bearer test-token");
});
Testing an Error Link
const errorLink = onError(({ graphQLErrors, networkError }) => {
// ...error handling
});
// Terminating link that returns an error:
const errorTerminator = new ApolloLink(() =>
Observable.of({ errors: [{ message: "Not authorized" }] })
);
const chain = ApolloLink.from([errorLink, errorTerminator]);
// Assert error handler was called, or that the operation was retried
Using MockLink for End-to-End Tests
@apollo/client/testing provides MockLink for component/integration tests:
import { MockedProvider, MockLink } from "@apollo/client/testing";
import { GET_USER } from "./queries";
const mocks = [{
request: { query: GET_USER, variables: { id: "1" } },
result: { data: { user: { id: "1", name: "Ada" } } },
}];
// In React component testing:
render(
<MockedProvider mocks={mocks} addTypename={false}>
<UserProfile userId="1" />
</MockedProvider>
);
await waitFor(() => {
expect(screen.getByText("Ada")).toBeInTheDocument();
});
MockedProvider replaces the real Apollo Client with one that matches operations to canned responses - no network calls.
Jest Setup Tips
// Helper to await a link chain result:
function executeLink(link: ApolloLink, op: any): Promise<any> {
return new Promise((resolve, reject) =>
execute(link, op).subscribe({ next: resolve, error: reject })
);
}
Why This Matters in Interviews
Apollo/GraphQL interviews use this to check that you understand the link middleware model. Key points: links are composable; test in isolation by creating a mock terminating link; use execute() + subscribe to trigger the chain; capture the operation passed to forward() to assert on header/context transforms; and use MockedProvider for component-level integration tests. Understanding the Observable-based API is the underlying concept.
Common Pitfalls
- Forgetting a terminating link - the chain hangs indefinitely (no one ever calls
.next()on the subscriber). - Not handling the async Observable with a
Promiseordonecallback in tests - test completes before the async assertion runs. - Testing the whole Apollo Client rather than isolating the specific link - slower, harder to pinpoint failures.
- Not resetting mock state between tests - mocks from one test interfere with the next.
References
- Apollo Links docs: https://www.apollographql.com/docs/react/api/link/introduction/
- Apollo Client testing: https://www.apollographql.com/docs/react/development-testing/testing/