With a REST API, a 200 usually means the request worked. With GraphQL it often only means the request arrived. A GraphQL server can answer 200 with an errors array in the body, sometimes alongside partial data. A test that checks the status code and stops there will pass while the query fails.
So I assert in three steps, in this order:
- Status. The HTTP request succeeded.
- Errors. The body has no
errorsfield, so GraphQL reported no problems. - Data. The values in
dataare the ones we expected.
Here is the idea in Playwright, against the public Countries API. It sends a named query with a required $code variable:
test('country query returns Canada', async ({ request }) => {
const response = await request.post('https://countries.trevorblades.com/', {
data: {
query: `query GetCountry($code: ID!) {
country(code: $code) { name capital currency }
}`,
variables: { code: 'CA' },
},
});
// 1. the request worked
expect(response.status()).toBe(200);
const body = await response.json();
// 2. GraphQL reported no errors
expect(body.errors).toBeUndefined();
// 3. the data is right
expect(body.data.country).toMatchObject({
name: 'Canada', capital: 'Ottawa', currency: 'CAD',
});
});
The order matters for the failure message. If the server returns errors, the test fails on step two and prints them, instead of failing on step three with a confusing “cannot read properties of null”.
What this doesn’t cover
This is one successful query. It doesn’t reproduce a 200-with-errors response, and it says nothing about permissions, invalid input or mutations. Error status codes also vary between servers and kinds of error, so don’t assume every GraphQL failure comes back as 200 either. The check above simply refuses to treat 200 as proof.
The full spec is in my playwright-lab repo.