Blog
/
Education

Integration Testing for GraphQL APIs, type-safe, run locally and in CI

cover
Jens Neuse

Jens Neuse

min read

Cosmo: Full Lifecycle GraphQL API Management

Are you looking for an Open Source Graph Manager? Cosmo is the most complete solution including Schema Registry, Router, Studio, Metrics, Analytics, Distributed Tracing, Breaking Change detection and more.

We've recently built a testing framework for GraphQL APIs. It's open source and available as part of our monorepo. I'd like to take the chance to explain the motivation behind it, how it works, and how you can use it to test your own GraphQL APIs locally and within your CI.

Actually, it's not just good for testing GraphQL APIs, as you can also use it to test REST APIs, Databases and more, but I'll focus on GraphQL APIs in this article.

Motivation

WunderGraph is a crossover between a Backend for Frontend (BFF) and a (GraphQL) API Gateway. We use GraphQL as the main interface to APIs and Databases. You can introspect one or more services into what we call the "Virtual Graph". The Virtual Graph is a composed GraphQL Schema across all your services.

By defining a GraphQL Operation, you're essentially creating a "Materialized View" on top of the Virtual Graph. Each of these views, we call them simply "Operations", is exposed as a JSON RPC Endpoint. That's why we call the graph a "Virtual Graph", because we're not really exposing it.

If you're building such a system, you'll want to make sure that it integrates well with all sorts of services. Especially when you allow your users to create Materialized Views across multiple services or data sources, you want to make sure that all of these integrations work as expected and that you don't break them with future changes.

How it works: Type-safe testing for GraphQL APIs

You can probably roll your own testing framework for GraphQL APIs, but WunderGraph comes with some unique features that make it really easy to write tests for GraphQL APIs.

Let's start with a simple GraphQL API.

Testing a simple monolithic GraphQL API

First, we need to tell WunderGraph about our GraphQL API. We do so by "introspecting" it into the Virtual Graph.

1
2
3
4
5
6
7
8
9
10

This will add the Countries GraphQL API to our Virtual Graph. Next, let's define a GraphQL Operation that we want to test.

1
2
3
4
5
6
7

This creates a Continents Operation, but not only that. We're also generating TypeScript models for this Operation and a type-safe client for it, which will be quite handy in the next step, when we want to write our tests.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

That's it. Before each run, we create a dedicated instance of WunderGraph, which is responsible to run the GraphQL Engine for the Virtual Graph and the JSON RPC Server. We then use the generated client to execute the Continents Operation and make sure that we get the expected result.

There are a few details to note here.

You might have noticed that the createTestServer function comes from the .wundergraph/generated/testing module, so this is actually a generated file. When we call wg.client(), the returned client is also generated, meaning that we can easily write type-safe assertions against the result.

This has a few advantages. First, we can use the TypeScript compiler to make sure that we're not making any mistakes. Second, we can use the TypeScript compiler to guard against breaking changes in the GraphQL API. When you change the GraphQL API, WunderGraph will re-generate the client and assertions might immediately fail if they are affected by the change.

Another thing to note is that we've designed the testing framework in a way that it's compatible not only with Jest, but also with other testing frameworks like Mocha, Jasmine, and more. We've also made sure that tests can run in parallel, otherwise testing might become a bottleneck in your CI.

To run the tests, we simply run npm test in our case. The full example can be found here .

If you go through the examples, you'll notice that there are a lot more examples with different setups. Let me go through some of them to illustrate the different use cases.

Integration Tests for Apollo Federation / Federated GraphQL APIs

WunderGraph supports Apollo Federation, so you can not just use WG as a BFF or API Gateway on top of your federated Subgraphs, but also use the testing framework to test your federated GraphQL APIs.

Here's an example configuration to introspect a few Subgraphs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

Next, let's define a GraphQL Operation that we want to test.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

And here's the test (abbreviated):

1
2
3
4
5
6
7
8

In this case, we're validating that we're getting back a list of 3 products.

Testing the Hasura GraphQL Engine

Testing the Hasura GraphQL Engine is a tiny bit more complicated to set up, because we need to inject a Header for authentication into each request, which we don't want to commit to our repository.

That said, testing your Hasura GraphQL API is very important. As Hasura generates a GraphQL API from the database structure, changing the database structure might break API consumers at any time. Writing tests against your database-generates GraphQL API will help you to catch these issues early.

This post is about testing GraphQL APIs, but I'd like to note that one of the great things about WunderGraph is that you can use it to decouple clients from an API implementation with its JSON RPC Middleware layer.

Back to topic, let's configure WunderGraph to introspect the Hasura GraphQL Engine.

1
2
3
4
5
6
7
8
9
10

Notice that we're using the headers builder to inject a Header into each request from the process environment. This is a great way to inject secrets into your tests without committing them to your repository. As WunderGraph generates a configuration file (json) for our Virtual Graph, we might be leaking secrets if we commit this file to our repository. By using the EnvironmentVariable builder, we create a placeholder for the secret in the generated config file and inject the secret at runtime.

Next, let's define an operation that we want to test.

1
2
3
4
5
6
7
8
9

And here's the test (abbreviated):

1
2
3
4
5
6
7
8

Testing Joins across multiple GraphQL APIs

WunderGraph is capable of joining data across multiple APIs, so let's test this feature as well.

In this case, we need to configure WunderGraph to introspect two APIs.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

Next, we define an operation that fetches a country by its code and joins the weather data for the capital city.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

Finally, we write a test that validates the result.

1
2
3
4
5
6
7
8
9
10
11

I really hope this test will never fail, although it's not unlikely that it will.

Testing Authentication for GraphQL APIs

Another feature that WunderGraph brings to the table is the ability to add authentication to an existing GraphQL API.

Here's an example configuration that adds token-based authentication to any GraphQL API.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

In this case, we're using an EnvironmentVariable to load a JSON Web Key Set (JWKS) from the process environment. If you're using a 3rd party authentication provider, JWKS is a great way to share information with a policy enforcement point (PEP) like WunderGraph.

But that's not all. WunderGraph also supports custom claims. Let's say that you want to use the tenant ID from your JWT to scope your GraphQL API. You can do this by adding a custom claim to your auth config, and then use the @fromClaim directive to inject the claim into your GraphQL API. Let's see how this works in a testable way.

1
2
3
4
5
6
7
8
9
10
11

WunderGraph ensures that the resulting JSON RPC API is only accessible to users that have a valid JWT with a tenant ID claim. A middleware will also enforce that the user doesn't send a tenantID variable as part of the request.

Great, we've built an API that we assume is secure. Let's ensure that this is actually correct (abbreviated version).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

I left out some of the complexity of the test setup, but I hope you get the idea.

A lot of frameworks give you very little tooling to test the correctness and security of your APIs automatically. We try to make this as easy as possible with WunderGraph.

Testing your GraphQL APIs in Continuous Integration

Adding to the previous section, we can also use WunderGraph to test our GraphQL APIs in a CI environment. E.g. you could use GitHub Actions to run your tests on every commit, on a schedule or with a webhook. Wouldn't it be great if you could automatically test the correctness of your GraphQL APIs every hour?

Here's the flow how this could work:

  1. Create a repository containing a WunderGraph application
  2. On each run, install the dependencies and call wunderctl generate to introspect all APIs and generate the configuration
  3. Run npm test to run the tests with your test runner of choice
  4. Run this workflow periodically and set up alerts for failed runs

Conclusion

Testing your GraphQL APIs is a great way to ensure that your APIs are correct and secure. Using WunderGraph, you can test multiple GraphQL and REST APIs in a single test suite with minimal effort. Thanks to the code-generation & code-first approach, we can spot errors early thanks to the type system and tsc compiler.

If you found this post interesting, you might want to follow me on Twitter or join our Discord Community and start a discussion.