Sitemap
Better Programming

Advice for programmers.

Snapshot Testing Your GraphQL API

3 min readAug 6, 2020

--

Zoom image will be displayed
Photo by Ferenc Almasi on Unsplash.

Snapshot testing was popularized by frameworks like Jest in order to quickly and effectively track unintended changes to React components. However, the concept can be taken away from the front end and find equal utility for back-end testing.

In this article, I’ll show you how to easily achieve large coverage of your API through integration tests using Jest snapshots in Node.js.

In the next few sections, we’ll cover:

  • The benefits of snapshot testing
  • How to set up a testing environment
  • Managing snapshots tests over time

So let's get started.

Benefits of Snapshot testing

Testing often has a sweet spot. The goal is rarely 100% coverage, but you should aim to use the right kind of testing and find a balance between fluidity and stability.

Unit testing is king when covering core parts of your application, and I’m not advocating that snapshots should be used here.

Rather their utility is better suited for integration testing and asserting that a given request returns a given response. Through doing this, you will gain wide coverage of all your resolvers while easily being able to change tests as your application grows.

Setting Up a Test Environment

We need a way to send requests to our GraphQL server and capture the response as a snapshot. I am using apollo-server-testing to do this, but you could use something like supertest instead.

To make fewer assumptions about your specific project, I will show an agnostic approach to snapshot testing. In reality, you would want to seed/tear down databases and mock third-party requests for each test.

Install our two core dependencies:

yarn add apollo-server-testing jest

We will need two files:

  1. The test cases, as GraphQL query strings for each request.
  2. Our test runner to process and record snapshots for each test case.

Test cases

At scale, we would likely create a different test file for each group of resolvers, but for now, let's just assume we have one file called resolvers.test.js.

Here, we can add our test cases, which are an array of objects containing the GraphQL query string and any variables associated with the query:

At the bottom of the file, you can see we pass the testCases into a function called runTestCases. We also provide a name for this set of test cases that will be applied to the outputted snapshots.

So let's go ahead and write the test runner itself.

Creating the test runner

As you know, snapshot testing is a really quick process. So in its simplest form, we will create a test client and pass each query into it. We then wait for the response and record it.

Now when running the jest command, Jest will record the response under __snapshots__/resolvers.test.js.snap:

From this point forward, you can quickly add and remove tests for each resolver. Depending on your use case, you could even seed/drop a database for each test, providing isolated end-to-end coverage.

Managing Snapshot Tests Over Time

As your project grows over time and large refactors take place, you can continue to run your tests and see that the snapshots provide the same output. If you need to update a snapshot, run jest -u, which will replace the content.

When updating snapshots, it is extremely important to validate that the new output is what you expect. I have seen many developers run jest -u and commit the code without a second thought. This can easily be caught through PR reviews. But when working alone, it's imperative to be strict with this process.

I use VSCode and have found the diff tool under the source control tab a fantastic way to monitor unintended changes to snapshots when completing refactors.

Extra Info

What do I pass into createTestClient ?

In the example above, I passed a method into createTestClient called createServer. This is just an ApolloServer instance containing your resolvers:

How do I handle mutations?

createTestClient returns an object containing { query, mutate }. You must pass the corresponding string into the correct method. In the testCases objects, you could add mutations under a key called mutate or write a small function to detect this from the string itself:

Let me know if you have any further questions.

--

--