and Contract Tests
GraphQL APIs without systematic tests are fragile: every schema change can silently break frontends. Query tests, snapshots, and contract tests together form a testing layer that catches regressions early and keeps frontend contracts stable over the long run.
Table of Contents
- 1. Why GraphQL testing is more than just trying a query
- 2. Query tests: the first step toward a stable API
- 3. Snapshot tests: regression protection for response structures
- 4. Contract tests: stable contracts between frontend and backend
- 5. Schema validation in the CI pipeline
- 6. GraphQL testing in Magento: integration tests and PHPUnit
- 7. Tooling overview: Jest, Vitest, Inspector, and more
- 8. Typical mistakes and how to spot them
- 9. Testing approaches compared
- 10. Summary
- 11. FAQ
1. Why GraphQL testing is more than just trying a query
Anyone who has run a GraphQL query manually in GraphiQL once and seen the result look correct doesn't have a test yet. That's exploration, not quality assurance. The difference becomes painfully clear three months later, when a different team member rewrites a resolver, renames a field, or changes the pagination structure, and the frontend silently starts producing errors because nobody knew of an automated check.
GraphQL testing spans several layers: query tests verify that a known request returns the expected result. Snapshot tests compare the current response structure against a stored reference result and raise an alarm on unexpected changes. Contract tests go further and define formal agreements between the frontend team writing queries and the backend team maintaining the schema. Together, all three approaches form a testing layer that makes schema evolution controllable and reduces surprises in production.
2. Query tests: the first step toward a stable API
A query test sends a defined GraphQL request to the API and checks the response against known expectations. In Node.js projects, this is typically done with Jest or Vitest together with an HTTP client library. The request is stored as a fixture file so it can be versioned and reviewed in code review. The most important part of building query tests is separating the test call from the assertion: store the response first, then check specific fields, rather than comparing the entire response as a string.
In Magento projects there's a dedicated base class for integration tests. The GraphQlQueryTest class provides a graphQlQuery() method that internally fires a full HTTP request against the running shop. That makes Magento GraphQL tests real integration tests: no mocking, no in-memory schema, but the actual resolver stack with a database behind it. That's slower, but far more meaningful than unit tests against isolated resolver classes.
# Query fixture: category-products.graphql
# Tests product listing for a specific category page
query CategoryProducts($categoryId: String!, $pageSize: Int!) {
products(
filter: { category_id: { eq: $categoryId } }
pageSize: $pageSize
currentPage: 1
sort: { position: ASC }
) {
total_count
page_info {
current_page
page_size
total_pages
}
items {
sku
name
url_key
price_range {
minimum_price {
regular_price {
value
currency
}
}
}
}
}
}
3. Snapshot tests: regression protection for response structures
Snapshot tests solve a concrete problem: you want to make sure a response structure still has the same shape after a schema change, without manually asserting every single field. On the first run, the current response is stored as a JSON file, the snapshot. On every subsequent run, the current response is compared against the stored snapshot. Deviations cause a test failure and must be explicitly confirmed.
The most common objection to snapshot tests is: "The snapshots go stale quickly anyway." That's true, but that's exactly the point. When a snapshot goes stale, a deliberate decision is required: is the change intentional? Then the snapshot gets updated. Is it unintentional? Then the snapshot test is the only mechanism that made the regression visible. The key is treating snapshots as source code and including them in the review process, not as annoying artifacts you blindly refresh with --updateSnapshot.
# Snapshot test: validates that the product type structure
# has not changed between deployments
query ProductSnapshotTest {
products(search: "testproduct-snapshot") {
items {
__typename
sku
name
url_key
meta_title
meta_description
price_range {
minimum_price {
regular_price { value currency }
final_price { value currency }
discount { amount_off percent_off }
}
}
media_gallery {
url
label
disabled
}
}
}
}
4. Contract tests: stable contracts between frontend and backend
Contract tests are the next stage beyond snapshot tests. Instead of just snapshotting the current response, both sides, frontend and backend, explicitly define which fields the frontend needs and which fields the backend must provide. That agreement is captured in a file and checked in the CI pipeline. GraphQL Inspector is the most common tool for this purpose: it checks whether every field used in the "consumer" document exists in the schema, whether its types match, and whether deprecated fields are still being used.
In practice, this works best when the frontend team stores its queries as fragment definitions in a shared repository. The backend team automatically checks, on every schema change, whether existing consumer queries are still valid. This combination makes breaking changes visible before they get deployed. Pact is an alternative for REST APIs; for GraphQL, GraphQL Inspector is the more natural choice because it uses the schema directly as the basis for the contract.
5. Schema validation in the CI pipeline
Schema validation in the CI pipeline means automatically checking, on every merge request, whether schema changes introduce breaking changes. Breaking changes are any changes that make existing queries invalid: removing fields, changing argument types to incompatible types, or removing types from unions. GraphQL Inspector can be wired into a GitHub Actions or GitLab CI pipeline with just a few lines of configuration and produces a clear report on the type and severity of the changes.
A pragmatic approach for teams that don't yet have a full contract testing setup: version the SDL of the current production schema as a file in the repository. On every deployment, the new SDL is compared against the old one. That's not a perfect solution, but it covers the most common cases and requires no external service. In Magento, you generate the SDL via bin/magento dev:graphql:schema and store it as a fixture in the test repository.
# Consumer query document, used by the frontend product listing page
# Contract: backend must provide all these fields
fragment ProductListItem on ProductInterface {
sku
name
url_key
small_image { url label }
price_range {
minimum_price {
final_price { value currency }
discount { percent_off }
}
}
rating_summary
review_count
}
query CategoryListing(
$filter: ProductAttributeFilterInput!
$pageSize: Int!
$currentPage: Int!
) {
products(filter: $filter, pageSize: $pageSize, currentPage: $currentPage) {
total_count
items { ...ProductListItem }
page_info { current_page total_pages }
aggregations {
attribute_code
label
count
options { label value count }
}
}
}
6. GraphQL testing in Magento: integration tests and PHPUnit
Magento ships a complete infrastructure for GraphQL integration tests. The foundation is the abstract class Magento\TestFramework\TestCase\GraphQlAbstract, which provides methods like graphQlQuery(), graphQlMutation(), and graphQlQueryWithResponseHeaders(). These tests run against a live Magento instance with test data from fixtures and exercise real resolver paths, including database access, caching, and plugin chains.
The Magento testing setup has strengths and weaknesses. The strength: tests are realistic and also surface database problems, missing indexer entries, and resolver bugs. The weakness: the tests are slow and require a running environment. For a fast feedback loop during development, it's advisable to run the slow integration tests in CI and explore locally against a running dev instance with Altair or GraphiQL. Unit tests for resolver classes are possible, but only worthwhile for resolvers that encapsulate complex logic; a simple resolver pass-through doesn't need a mocking test.
7. Tooling overview: Jest, Vitest, Inspector, and more
The GraphQL testing tool landscape is manageable once you organize it by task. For query tests and snapshot tests on the Node.js side, Jest and Vitest are the common test runners. Both support snapshots natively. For pure API tests without any frontend code, Hurl is a lean alternative: HTTP requests as a text file with embedded assertions, versionable and readable without any JavaScript knowledge.
For schema validation and contract checks, GraphQL Inspector is the most mature tool. It offers command-line tools, a GitHub check, and a web UI. Rover CLI from Apollo is an alternative that's tightly integrated with Apollo Studio and covers schema checks as part of the Apollo ecosystem. For teams without an Apollo stack, GraphQL Inspector is the more neutral choice. An often-overlooked tool is graphql-tag combined with TypeScript: when queries are compiled with type safety, the compiler catches many breaking changes at build time, before any tests even run.
8. Typical mistakes and how to spot them
The most common mistake when building GraphQL tests is testing only the "happy path": the query returns data, everything looks fine. What's missing: tests for error cases. What happens when a product doesn't exist? Does the API return an empty array or null? Does it return an error in the errors array or in the data field? In GraphQL, both are valid, depending on the implementation. Contract tests must also define the error structure, not just the success structure.
A second typical problem: tests that run against production data. Production data changes, and tests that depend on it are unstable. The solution is a clean test fixture strategy: test data gets loaded before the test and deleted afterward. Magento offers the @magentoDataFixture annotation mechanism for this. A third mistake is missing tests for auth cases: which fields are only visible to logged-in customers? Contract tests should explicitly check that protected fields return an error rather than a value when there's no token present.
| Test Type | What Gets Checked | Recommended Tool | When It Makes Sense |
|---|---|---|---|
| Query Test | Concrete response to a known request | Jest / Vitest / Hurl | Always, the first test for every query |
| Snapshot Test | Response structure unchanged | Jest Snapshots | For stable response types |
| Contract Test | Frontend queries valid against the schema | GraphQL Inspector | Critical frontend consumers |
| Schema Diff | Breaking changes between versions | Inspector / Rover CLI | Before every schema deployment |
| Integration Test | Full resolver stack with database | Magento PHPUnit | Critical resolvers in Magento |
9. Testing approaches compared directly
The most common question when building a GraphQL testing strategy is: "Do we really need all three layers?" The answer depends on the team and the API's maturity. A single developer maintaining an internal API can get by with simple query tests for a long time. A team with several frontend consumers, evolving the schema independently, needs contract tests, because otherwise coordinated deployments become the only safety net.
The most important practical takeaway: GraphQL testing is not a one-time setup, it's a process. Tests go stale as the schema grows and nobody updates the fixtures. The testing strategy needs to be explicitly agreed on within the team: who's responsible for which testing layer? That sounds like overhead, but it prevents the most common real-world situation: nobody wrote tests because everyone assumed someone else would.
GraphQL Testing: Queries, Snapshots and Contract Tests, the Key Points at a Glance
Query Tests
Check a defined GraphQL request against known expectations, the first step toward a stable API, also in Magento via PHPUnit integration.
Snapshot Tests
Store the response structure as a reference; every unexpected change causes a test failure and forces an explicit confirmation.
Contract Tests
Frontend consumer queries get validated against the schema; breaking changes are caught before deployment, not first in production.
CI Integration
Schema diffs, Inspector checks, and integration tests automated in the pipeline, no manual review of every schema change required.
10. Summary
GraphQL testing is a topic many projects address too late. As long as the API is small and the team is small, exploratory use of GraphiQL is enough. Once multiple teams work against different consumers or the schema is evolved regularly, query tests, snapshot tests, and contract tests are no longer optional, they're necessary. The difference between a schema that can be changed at any time and one that keeps frontends stable almost always comes down to test coverage.
In Magento projects, the built-in test infrastructure provides a solid starting point. The combination of Magento integration tests for resolver correctness, GraphQL Inspector for schema evolution, and snapshot tests for response stability covers the most important risks. The next step after this article: pick a single critical query, write a snapshot test, and integrate it into the CI pipeline, that's the first concrete step toward a stable GraphQL testing strategy.