Easy integration testing of GraphQL APIs with Jest
By: Philipp Schmiedel
Tags: Testing, Integration, GraphQL, Jest, Software Development

In modern software development, you know for sure that automated tests are crucial for the success of your software project. But writing unit tests is only part of the story, as you also need to check if all the code you’ve written works well together and that your service as a whole actually does what it’s supposed to from a business logic perspective.

This article shows you one (of many) ways to write integration tests for your GraphQL API. I’ll give you also some introduction how a good test looks like and why I’ve chosen this particular toolset. This approach can of course also be used for any other micro service or API variant.

Integration testing

When writing unit tests you always test only the logic in the current method. All calls to external methods should be mocked, so you actually do not write into your database or call an external service for real. Integration tests come in different flavors: you might want to mock away some calls to external services (e.g. a payment API that does not offer you a sandbox server), however, I try to keep the integration testing system as close as possible to reality. Your goal when writing integration tests is to test the typical scenarios (and edge cases) of your service in a way your service will be used in the production environment later.

Good integration tests are:

Isolated: Testing one scenario has no effects on another scenario and it does not matter in which order the different integration tests are executed

Testing one scenario has no effects on another scenario and it does not matter in which order the different integration tests are executed Repeatable: When code is not changed, the test result is always the same

When code is not changed, the test result is always the same Understandable: It is easy to understand what business case the particular test covers and eventually why the expected outcome is important

It is easy to understand what business case the particular test covers and eventually why the expected outcome is important Structured: Test files should not have too many lines, a more complex scenario should be covered in its own test file

Test files should not have too many lines, a more complex scenario should be covered in its own test file Easy: The tests should be easy to implement and easy runnable by the developer team (testing is not the most favoured task for many developers, so keep the barrier as low as possible)

Test database and seed data (example MongoDB)

As said, you need to ensure that your tests can run isolated and are repeatable. Therefore you need a test database that has the same dataset every time before a new test scenario is executed, a dataset where you know that e.g. a specific shop item is existing and you can write a test which tries to retrieve this item with ID “xyz”.

It may sound like a lot of work to create this setup, but it is easy to achieve. First you need an empty test database by booting up a local docker container or an additional database instance at your database provider. Second you create a database dump from your production database with some example data. In case of MongoDB it can be done with one simple command:

Just replace the parameters according to your database setup. Take particular note of the query argument to just select a subset for the export if your production database contains a lot of data.

Importing the data into your test database is as easy as creating the seed file. So finally we can create a shell script we can call anytime to create exactly the same dataset in our database again like this:

Caution: Always double check the server address or use database logins that do not exist in the production database to prevent accidental deletion of your production data!

Jest integration testing with snapshots

First of all, you might ask why are we using Jest for integration testing, there are a lot of other tools out there. I see a big benefit in re-using a framework your developers might already know from unit testing and do not have to learn another framework. This can keep the entry barrier (also for new developers) low and save a lot of time. Additionally, Jest provides with Snapshots a really easy and fast way to create integration tests.

So let’s say we have a GraphQL endpoint that gives back all the items available in our store. Thanks to our seed data we know what to expect:

Request:

Response:

Unless we change our code or our seed data, the API should return this exact response to this exact request. Jest Snapshots gives us an easy possibility to check the complete response object. Additionally we need to ensure the dataset is correct before running our test using the mentioned seed script. Altogether, our first test scenario could look like this:

The first run

First run creates the snapshot file

When running this test the first time, Jest will automatically create a __snapshots__ folder and a snapshot file inside of it. Just because the console shows you a green test, it does not mean you are done yet! You have to check if the auto-generated snapshot contains what you expect, else you would assume a false state to be correct. This check is necessary every time you’ve written a new test and the console is showing you that new snapshots have been written. In our case, the snapshot shows the correct data and we can commit the snapshot file together with our tests into our repository.

The n-th run (passing)

Running our test again without changing any code is the actual real integration test. Jest validates that the response from the API is matching exactly what was stored in the snapshot file.

The n-th run (failing)

The more interesting case is when the snapshot does not match anymore and your test is failing:

Failing snapshot test after code changes

As the diff shows, the order of the response seems to has changed and is therefore not matching our expected response anymore. This can have two reasons:

The snapshot is correct and you introduced a bug with your latest

code changes. In this case, the test prevented the go-live of a bug and you can adapt your code until the test is green again

code changes. In this case, the test prevented the go-live of a bug and you can adapt your code until the test is green again The snapshot is wrong because the business requirement has changed (e.g. the default sorting shout be by name ASC now). In this case, you have to update the snapshot to confirm the change is correct by running Jest with the -u flag and committing the updated snapshot file into your repository.

Updating a snapshot after confirming the change is correct

Learnings from daily life

Using Jest Snapshots is a fast way to write integration tests for our GraphQL API. However, if you do your research you will find a lot of valid arguments why not to use Jest Snapshots like in this blog post What’s wrong with snapshot tests. While some points are true also for this use case, I think that testing APIs with Jest Snapshots is a bit different and some downside can be prevented. Here are my learnings from using snapshots in a real project:

Jest expects the snapshot to match exactly. As GraphQL gives us the power to request only a subset of parameters, you should only request those parameters that are relevant for your test. This prevents the test from failing for the wrong reasons.

… this will also make your snapshots errors easier to read as the diff contains fewer data.

Jest runs in parallel mode per default. If you have two test scenarios (two spec.ts files) writing into the same database collection or table you might get side effects. To ensure isolation of tests run Jest in sequential mode via the --runInBand flag.

files) writing into the same database collection or table you might get side effects. To ensure isolation of tests run Jest in sequential mode via the flag. Most importantly: Make sure your team does understand snapshots! The tests can be written really fast but this comes at the price that snapshots do contain a lot implicit knowledge. Your team needs to think more about the real reason for the failing snapshot, its impact etc. before blindly running jest -u to update the snapshot.

Integration tests have become an important part in our tool chain to prevent releasing bugs that have been left undiscovered before when just using unit testing. Feel free to leave your experiences with Jest Snapshots or other integration testing tools in the comments.