Testing Magento GraphQL: PHPUnit, Integration Tests and Real Requests
AI generated
{ }
type
GraphQL · Magento · PHPUnit · Testing · Integration Tests
Testing Magento GraphQL
PHPUnit, Integration Tests and Real Requests

Untested GraphQL resolvers are a ticking time bomb: schema changes silently break frontend contracts, and authentication bugs stay hidden until production. This article shows how to test Magento GraphQL on three levels, from unit tests for resolver logic to real HTTP requests against the endpoint.

18 min read PHPUnit · graphQlQuery() · Fixtures · Mocking · curl · Contract Tests Magento 2.4 · PHP 8.x · PHPUnit 10+

1. Test strategy: three levels for GraphQL resolvers

GraphQL resolvers in Magento can be tested on three levels that differ in speed, isolation and how much confidence they provide. Unit tests are fast and isolated, but they only test a resolver's internal logic without any real database access. Integration tests run inside the Magento framework context with a real test database and are more realistic, but noticeably slower. HTTP tests against the real GraphQL endpoint are closest to production behavior, but require a running Magento instance.

A sensible test strategy combines all three levels: unit tests for resolver logic and the service layer, integration tests for the connection between resolver, repository and schema, and at least one HTTP test per query and mutation as a smoke test in the deployment process. Anyone testing only one level, most commonly skipping integration tests, is left with a gap that does not surface Magento-specific bugs such as incorrect di.xml configuration or schema merge conflicts.

2. Unit tests: resolver logic without the Magento framework

Magento resolvers should be structured so their logic can be tested without bootstrapping the entire Magento framework. That means moving business logic into services that have no Magento-specific dependencies, and keeping resolvers as a thin delegation layer. The service can then be tested with PHPUnit and simple mock objects, without starting a Magento instance at all.

In practice this often fails because resolvers use repositories or the ObjectManager pattern directly, both of which are hard to mock. The key is to inject dependencies through the constructor and to type them against interfaces rather than concrete classes. A resolver class that has a CustomerBadgeServiceInterface injected can be tested with a mock implementation of that interface, fast, precise and without any framework overhead.


# The query under test, used in both integration tests and HTTP smoke tests
query CustomerBadgeTest {
  mironCustomerBadge(customer_id: 1) {
    badge_level
    points
    next_level_threshold
    expires_at
  }
}

# Mutation test example
mutation UpdateBadgeLevelTest {
  mironUpdateBadge(input: {
    customer_id: 1
    badge_level: "gold"
  }) {
    success
    message
    updated_badge {
      badge_level
      points
    }
  }
}

3. Integration tests with graphQlQuery()

Magento provides a base class for GraphQL integration tests, Magento\TestFramework\TestCase\GraphQlAbstract, which provides the graphQlQuery() and graphQlMutation() methods. These methods send real GraphQL requests against the test instance, decode the response, and return it as a PHP array. That allows precise assertions on the exact response structure without having to write curl commands.

Integration tests of this kind run against Magento's test database, which is set up fresh for each test run or filled with data through fixtures. They are noticeably slower than unit tests, a single integration test often takes 2 to 10 seconds, but they check exactly the things that go wrong in practice: wrong FQCNs in di.xml, schema merge conflicts, missing permissions, and incorrect response structures. Every new resolver should have at least one integration test that covers the happy path and one failure case.


# Integration test query, used with Magento's graphQlQuery() method
# Tests the full stack: schema → resolver → service → repository → database
query IntegrationTestProducts {
  products(
    filter: { sku: { eq: "test-product-001" } }
    pageSize: 1
  ) {
    total_count
    items {
      sku
      name
      price_range {
        minimum_price {
          final_price { value currency }
        }
      }
    }
  }
}

# Assert in PHPUnit:
# self::assertEquals(1, $response['products']['total_count'])
# self::assertEquals('test-product-001', $response['products']['items'][0]['sku'])
# self::assertNotEmpty($response['products']['items'][0]['price_range'])

4. Test fixtures: setting up test data cleanly

Integration tests need consistent test data. Magento provides the fixture system for this, using @magentoDataFixture annotations. A fixture PHP file creates products, categories, customers, or other entities in the test database and is automatically rolled back after the test. The Magento framework uses database rollback transactions to reset the test database to its starting state after every test.

For GraphQL resolver tests, product and customer fixtures matter most. Product fixtures should set every attribute the resolver returns, otherwise tests can produce false positives because null values happen to match the expected result. Customer fixtures must create real customer entities, not just IDs, because the Magento authentication stack needs the complete customer record. Well-written fixtures are declarative and commented, they describe which starting state they create, not how they technically do it.

5. Testing authenticated queries

Queries and mutations that require an authenticated customer must be tested with a bearer token. In integration tests, Magento provides the method graphQlQuery($query, [], '', ['Authorization' => 'Bearer ' . $token]). You obtain the token either through a generateCustomerToken mutation or directly via the token service. For integration tests that need a customer, combining a customer fixture with token generation is the standard approach.

It is especially important to also test the negative case: what happens when a query that requires an authenticated context is called without a token? The resolver should respond with a GraphQlAuthorizationException, which shows up in the response's errors array. This test checks whether the security check is actually implemented, not just the positive path, which is a common testing blind spot.

6. Testing error scenarios and exception types

Testing error scenarios is at least as important as testing the happy path. For GraphQL resolvers there are three typical classes of errors that should each be tested explicitly: input errors (invalid arguments, missing required parameters), entities not found (a product ID that does not exist), and authorization errors (access without a token or with the wrong scope). Each of these cases should lead to a specific exception class that Magento converts into a structured GraphQL error message.

In integration tests you catch these errors either with PHPUnit's expectException() method, or by checking the response's errors array when the test needs to keep processing the response despite the error. The latter makes sense when you want to test the exact error message and category: self::assertEquals('graphql-authorization', $response['errors'][0]['category']). This level of precision ensures that frontends can rely on a well-defined error structure.

7. Test types compared

Each test type has its own specific role in the GraphQL testing strategy. Choosing the right test type for the right purpose avoids both excessively long test runs and coverage gaps.

Test type Speed What it checks What it does not check
Unit test (PHPUnit) < 100 ms Resolver logic, service methods, data validation Schema, di.xml, database queries
Magento integration test 2 to 10 s Schema, resolver, repository, authentication HTTP stack, Varnish caching, load behavior
HTTP smoke test (curl) 0.5 to 2 s Endpoint availability, HTTP stack, response format Edge cases, data correctness, error scenarios
Contract test variable Schema compatibility with frontend queries Database content, performance, authentication
Load test (k6, JMeter) Minutes Performance under load, caching behavior Business correctness, error cases

8. Real HTTP requests: curl and Postman

For quick manual tests and as a deployment smoke test, real HTTP requests with curl or Postman work well. A simple curl call against the GraphQL endpoint immediately shows whether the endpoint is reachable, whether the schema knows your query, and whether the response has the expected structure. This test can be added directly to the deployment script as a final step, preventing a broken configuration from silently reaching production.

Postman additionally lets you save and version collections of queries that document the entire GraphQL module while also serving as a manual test base. Combined with Postman environments, the same query collection can be run against development, staging, and production. For teams, this is a pragmatic way to combine GraphQL documentation and testing without introducing a separate tool.


# HTTP smoke test queries, run these with curl after every deployment

# 1. Verify endpoint is up and schema is valid
# curl -X POST https://shop.example.com/graphql \
#   -H 'Content-Type: application/json' \
#   -d '{"query":"{ __typename }"}'

# 2. Verify custom query is registered
query SmokeTestCustomQuery {
  mironCustomerBadge(customer_id: 1) {
    badge_level
    points
  }
}

# 3. Verify authenticated query with Bearer token
# curl -X POST https://shop.example.com/graphql \
#   -H 'Content-Type: application/json' \
#   -H 'Authorization: Bearer <customer-token>' \
#   -d '{"query":"{ customer { firstname email } }"}'

# 4. Verify error response structure for unauthorized access
query VerifyAuthError {
  customer {
    firstname
  }
}
# Expected: errors[0].category = "graphql-authorization"

9. Contract tests: protecting frontend contracts

Contract tests check whether the current GraphQL schema is still compatible with the queries the frontend uses. The basic idea: frontend queries are saved as GraphQL documents and regularly validated against the current schema. If a schema change removes or renames a field that a frontend query uses, the contract test fails, before the change is deployed.

GraphQL Inspector is a practical CLI tool for this purpose: graphql-inspector validate schema.graphql queries/*.graphql validates every saved query against the current schema. In a CI/CD pipeline that runs after every merge, this prevents unintended breaking changes. For Magento projects with multiple frontends, for example a PWA frontend and a mobile app, this practice is especially valuable, because schema changes could otherwise break both clients at once.

10. Summary

A complete test strategy for Magento GraphQL combines unit tests for resolver logic and services, integration tests with graphQlQuery() for the full stack, HTTP smoke tests for deployment verification, and contract tests for schema compatibility. Each level has its own specific strength and catches errors that the other levels do not see.

The most common mistake in Magento projects: there are unit tests for services, but no integration tests for resolvers. That leaves configuration mistakes in di.xml, wrong FQCN entries, and schema merge conflicts undetected. A single integration test per resolver, happy path plus one failure case, substantially increases confidence in deployments and makes refactoring safer.

Magento GraphQL Testing: The Essentials at a Glance

Unit tests

Test resolver logic and services in isolation. Inject dependencies through interfaces so mocks are possible. Fast and precise for logic errors.

Integration tests

GraphQlAbstract and graphQlQuery() for real stack tests with a test database. Mandatory for every new resolver, at least happy path plus one failure case.

HTTP smoke tests

curl or Postman after every deployment. Check endpoint availability, schema validity, and response format in the running system.

Contract tests

GraphQL Inspector validates frontend queries against the current schema. In a CI/CD pipeline: prevents breaking changes before they are deployed.

11. FAQ: Testing Magento GraphQL

1Which base class for GraphQL integration tests?
Magento\TestFramework\TestCase\GraphQlAbstract, provides graphQlQuery() and graphQlMutation(), which send real requests and return the response as a PHP array.
2Test a resolver without the Magento database?
Move the logic into a service that implements an interface. Mock the service with PHPUnit. The resolver stays a thin delegation layer without logic of its own.
3What is @magentoDataFixture?
A PHPUnit annotation that includes PHP files with test setup. Data is reset after the test through a rollback transaction, a clean test database after every test.
4How to test a query with a bearer token?
graphQlQuery($query, [], '', ['Authorization' => 'Bearer ' . $token]). Generate the token in the test via the generateCustomerToken mutation or the token service.
5Correctly test a missing token?
Call without an Authorization header, then check errors[0].category for graphql-authorization. Or use expectException(GraphQlAuthorizationException::class).
6What is a GraphQL contract test?
Checks whether frontend queries are still compatible with the current schema. GraphQL Inspector validates saved query files, fails when fields have been removed or renamed.
7How to run integration tests?
bin/magento dev:tests:run integration or vendor/bin/phpunit -c dev/tests/integration/phpunit.xml. A separate test database must be configured.
8A dedicated integration test for every query?
Yes, at least a happy path plus one failure case. For complex resolvers, also test edge cases and validation rules.
9How to avoid slow integration tests?
Keep fixtures minimal, avoid excessive amounts of data, keep the test database on a RAM disk. Run integration tests in parallel across separate CI jobs.
10Use Postman for Magento GraphQL tests?
Yes. Postman supports GraphQL natively. Export collections and run them with Newman in CI pipelines. Good for smoke tests and manual exploratory testing.