Recently a colleague and I collaborated on a unique approach to doing GraphQL integration testing. Our unit testing setup already had the ability to test GraphQL resolver functions or helper functions used by the resolvers. Our end to end testing can be done easily by bringing up a GraphQL server and running queries against it while it resolves those queries by accessing a real database or REST api. What we designed for integration testing is somewhere in between.
Our approach gives you an end to end test of full GraphQL queries and mutations while automatically mocking external resources for you and comparing results against snapshotted data.
Goal: Test Full Queries With Easy Test Creation
The goal was to test a full query or mutation without using any external resources – no databases, no REST apis, etc. We also wanted to make test creation easy – as easy as normal jest snapshot testing if possible.
Mock External Resources and Snapshot Results
We mocked external resources by running queries against a live system and capturing the interactions with external resources. SQL queries and responses were recorded. REST calls were recorded. After capturing the calls and responses we saved them in special directory similar to jest snapshots. Each test case file would have a captured data file in the captured traffic directory. When the test ran a 2nd time, the captured data would be used to prime the code where we made external calls by using sequealize and fetch wrapper objects. When fetch is called, it uses the primed data to return the previously captured data. Likewise, when sequelize is called, the sequelize wrapper would use the previously primed data to return the previously captured data.
Start a GraphQL Server with Express Handlers
Next we started up our GraphQL server with some special express handlers that allowed us to prime or start/stop capture of interactions with external resources. We could have created GraphQL apis to prime/start/stop capture but we didn’t want to modify the public API that our customers interacted with. Instead of modifying GraphQL to allow us to prime/start/stop capture we instead added conditional express handlers used only during integration testing runs.
First Run – Capture Data
When a test case is run without previously capture external data, the express server is told to start capture mode. The query is run and the external interactions are captured. After the query is run, the test case requests the just captured data and saves it in the captured data directory next to the test case.
Subsequent Runs – Use Captured Data To Prime Sequelize and Fetch Wrappers
The second time the test case is run, it primes the express server with the previously capture data, runs the query which uses the previously captured data for all external interactions and then compares the results with the snapshotted results. This approach effectively tests all things inside GraphQL related to the query without using any external resources.
Putting It All Together
- Start up a GraphQL server with special express handlers
- Run all integration test queries/mutations against the GraphQL server just started
- If a test is run without previously captured data:
- Use the express handlers to start of capture of external interactions via REST or sequelize
- Run the test
- Get the captured data and save it in a special directory similar to jest snapshotted data
- Use the express handlers to stop capture of external interactions
- Run the next test
- If a test is run with previously captured data:
- Use the express handlers to prime the sequelize/REST wrappers with previously the captured data
- Run the query/mutation which will use the previously captured data
- Compare the results with the previously snapshotted results
- Run the next test
- Stop the GraphQL server