Cypress can’t directly "intercept" GraphQL requests in the same way it intercepts standard HTTP requests because GraphQL queries are typically sent as POST requests with a JSON payload to a single endpoint, and Cypress’s cy.intercept() command looks for specific request properties like method, URL, and request body.
Here’s how you can effectively test your GraphQL interactions using Cypress, focusing on the underlying HTTP requests:
The GraphQL Request Anatomy
When your frontend application makes a GraphQL request, it usually looks something like this:
- Method: POST
- URL: A single endpoint, often
/graphqlor/api/graphql. - Headers:
Content-Type: application/json, and oftenAuthorizationor other custom headers. - Body: A JSON object containing at least
query(the GraphQL query string) and optionallyvariablesandoperationName.
Cypress intercepts these as standard HTTP POST requests. The key is to identify which GraphQL request you want to intercept based on its unique characteristics.
Identifying Your GraphQL Requests
The most reliable way to target a specific GraphQL request is by its query string or its operationName.
Example Frontend Code (Conceptual):
// Using Apollo Client
const GET_USER_QUERY = gql`
query GetUser($id: ID!) {
user(id: $id) {
id
name
email
}
}
`;
client.query({
query: GET_USER_QUERY,
variables: { id: '123' },
operationName: 'GetUser' // This is a great identifier!
});
In this example, GetUser is the operationName, and the query string is the actual GraphQL query. You can use either to tell cy.intercept() which request to match.
Intercepting and Mocking
Let’s say you have a component that fetches user data.
cypress/support/commands.js:
Cypress.Commands.add('interceptGetUser', (userId = '123', mockData) => {
const operationName = 'GetUser';
const query = `query GetUser($id: ID!) { user(id: $id) { id name email } }`; // Or match a substring
cy.intercept('POST', '/graphql', (req) => {
// Check if it's the specific query and variables we're looking for
if (req.body.operationName === operationName && req.body.variables && req.body.variables.id === userId) {
req.reply({
statusCode: 200,
body: {
data: {
user: mockData || {
id: userId,
name: 'Mock User',
email: 'mock@example.com',
},
},
},
});
}
}).as(operationName); // Alias for waiting
});
cypress/e2e/user_profile.cy.js:
describe('User Profile Page', () => {
it('should display user information', () => {
const userId = '456';
const mockUserData = {
id: userId,
name: 'Jane Doe',
email: 'jane.doe@example.com',
};
// Use the custom command to intercept
cy.interceptGetUser(userId, mockUserData);
// Visit the page that triggers the request
cy.visit(`/users/${userId}`);
// Wait for the request to be intercepted (optional but good practice)
cy.wait('@GetUser');
// Assert that the data is displayed correctly
cy.contains('Jane Doe').should('be.visible');
cy.contains('jane.doe@example.com').should('be.visible');
});
});
Explanation:
cy.intercept('POST', '/graphql', ...): We target all POST requests to/graphql.req => { ... }: This is the callback function where we inspect the incoming request.if (req.body.operationName === operationName && req.body.variables && req.body.variables.id === userId): This is the crucial part. We check the requestbodyto ensure it’s the specific GraphQL operation we want to mock. Matching byoperationNameis often cleanest. IfoperationNameisn’t consistently used, you might need to match against a portion of thereq.body.querystring.req.reply(...): If the request matches, we send back a mocked response. Thebodystructure must match what your frontend expects from a GraphQL server (usually{"data": {...}}or{"errors": [...]})..as(operationName): This gives the intercepted request an alias (@GetUser) that we can use withcy.wait().
Advanced Matching
-
Matching by Query String: If
operationNameisn’t available or reliable, you can match against thequerystring. Be careful with exact matches, as whitespace can vary. Usingreq.body.query.includes('GetUserQueryName')or a more robust regex might be necessary.cy.intercept('POST', '/graphql', (req) => { if (req.body.query && req.body.query.includes('GetUser') && req.body.variables && req.body.variables.id === userId) { // ... reply } }); -
Multiple Intercepts: You can set up multiple intercepts for different GraphQL operations on the same page. Ensure your matching logic is specific enough to target the correct one.
-
Testing Errors: Mocking GraphQL errors is just as important.
cy.intercept('POST', '/graphql', (req) => { if (req.body.operationName === 'GetUser' && req.body.variables && req.body.variables.id === userId) { req.reply({ statusCode: 200, // GraphQL errors often return 200 OK body: { errors: [{ message: 'User not found', locations: [{ line: 2, column: 3 }], path: ['user'] }], data: null, // Or omit data entirely }, }); } });
The Counterintuitive Part
Many developers assume they need to parse the entire GraphQL query string within cy.intercept to find the exact operation. However, most GraphQL clients include an operationName field in the JSON payload precisely for this purpose. Relying on operationName is significantly more robust and easier to maintain than trying to dynamically parse and match complex GraphQL query strings, especially when dealing with fragments, directives, or variations in whitespace. If your client doesn’t send operationName, consider configuring it to do so, or falling back to matching known substrings within the query field.
Next Steps
Once you’ve mastered mocking GraphQL requests, you’ll want to explore how to stub out network requests that are not GraphQL, such as REST endpoints used for authentication or other auxiliary services.