DEV Community

Cover image for How to write unit, integration and e2e tests for your GraphQL API
Achraf
Achraf

Posted on • Updated on • Originally published at blog.escape.tech

How to write unit, integration and e2e tests for your GraphQL API

GraphQL is becoming more and more popular to build amazing products with a modern stack, and that makes total sense (that’s the path we took at Escape as well 😎)

But as with any new technology, it comes with the downside of not having a lot of good resources.

So today, we address one of the most critical aspect: testing your GraphQL API!

This was originally posted on blog.escape.tech

Why bother testing?

Testing your API is important to make sure that the business logic of your software is running properly.

Writing tests will allow you to reveal bugs before they make it to production - which we absolutely want to avoid (especially if you deploy on Fridays 😬).

With tests, you can verify functionalities, reliability, performance, security, and all sorts of potential issues.

Finally, keeping a good testing coverage will ensure that future updates of your API are not breaking the frontend applications consuming it.

How is it different from REST?

The primary difference is that REST APIs have several endpoints to test, but in GraphQL you only have one (in most cases). Therefore, the structure of your tests will be different from the usual REST APIs.

Another important difference is that GraphQL is a strongly-typed language that makes it extremely convenient to detect bugs as you code, especially when working on a massive codebase.

These differences make testing REST and GraphQL APIs fundamentally different. Now let’s see how it actually works!

How to test GraphQL APIs

GraphQL APIs have 3 different layers to test:

  • schema: testing your type definitions,
  • operations: testing the definition of your queries, mutations and subscriptions (in isolation from the actual execution logic),
  • resolvers: testing the last bit, the Javascript functions that resolve the operations (queries, mutations and subscriptions)

Our Example

Let's build the next Facebook (such a boomer move...)

Our graph of entities is going to look something like this:

And our application will need features like sending a friend request (a mutation), and displaying a feed based on a users's friends' posts (a query - get latest posts from friends).

Github Repository

If you want to follow along, you can clone the repository here: https://github.com/achrafash/graphql-testing
and follow the instructions from the README file to install and run the project locally.

Testing schema

GraphQL is a strongly-typed language, which makes it very handy to detect errors directly from your IDE.

To validate our schema we can use two libraries:

  • eslint-plugin-graphql: a linter to extend our Eslint rules,
  • graphql-schema-linter: a CLI tool to validate our schema definition.

Here's how to install them:

npm i -D eslint-plugin-graphql graphql-schema-linter
Enter fullscreen mode Exit fullscreen mode

Testing queries and mutations

You can start with basic, manual testing on GraphiQL. But at some point, automated tests are a must.

The best way to isolate your queries and mutations is to use EasyGraphQL Tester, a library to run your GraphQL code without firing up a server.

npm i -D easygraphql-tester
Enter fullscreen mode Exit fullscreen mode

We are not yet looking at how we are going to execute these queries and mutations to get or modify data, just their definition:

  • name of the operation,
  • their arguments (name and type),
  • the returned type.

Here’s what it looks like:

describe('Queries', () => {
            let tester
            beforeAll(() => {
                tester = new EasyGraphQLTester(schemaCode)
            })

            test('Should get friends with a nested query', () => {
            const query = `
                query GET_FRIENDS($userId: ID!){
                    user(id: $userId) {
                        id
                        friends {
                            id
                            firstname
                            lastname
                        }
                    }
                }
            `
            tester.test(true, query, { userId: '0' })
        })
})
Enter fullscreen mode Exit fullscreen mode

Testing resolvers

Resolvers are simply Javascript functions that we can test like any other Javascript code. Let’s go through an example with our resolver that gets the friends of a user:

it('Should get friends of user from their id', () => {
      const userId = 0
      const friends = getFriends(db, userId)
      expect(friends.length).toBe(1)
      expect(friends[0]).toHaveProperty('id')
      expect(friends[0]).toHaveProperty('firstname')
      expect(friends[0].firstname).toBe('Eduardo')
})
Enter fullscreen mode Exit fullscreen mode

Integration tests

Now we can put it all together with an integration test to make sure that all the components - schema, the query, and the resolver - are working properly:

it('Should return friends of the user with given id', async () => {
        const query = `
            query GET_FRIENDS($userId: ID!) {
                user(id: $userId) {
                    id
                    friends {
                        id
                    }
                }
            }
        `
        const args = { userId: '0' }
        const result = await tester.graphql(query, undefined, { db }, args)

        expect(result.data.user.id).toBe(args.userId)
        expect(result.data.user.friends).toBeInstanceOf(Array)
        expect(result.data.user.friends).toContainEqual({ id: '1' })
})
Enter fullscreen mode Exit fullscreen mode

E2E test

We’ve seen how to test the GraphQL part of our API, but we left off an important aspect of it: the HTTP server.

The frontend applications will consume our API through HTTP requests, and it wouldn’t be a proper API testing tutorial without covering that part as well.

This last part includes:

  • testing the request headers and body,
  • testing the response status and payload.

This time, we need a running server. We can use supertest, a library that does just that:

npm i -D supertest
Enter fullscreen mode Exit fullscreen mode

Now we can use it to run our server and send HTTP requests in our tests:

const supertest = require('supertest')
const app = require('./app')

app.server = app.listen(5000)

describe('Server', () => {
    afterEach(() => app.server.close())

    it('Should listen to HTTP requests', (done) => {
        const userId = '0'
        request(app)
            .post('/graphql')
            .send({
                query: `{ user(id: ${userId}) { id, friends{ id } }  }`,
            })
            .set('Accept', 'application/json')
            .expect(200)
            .end((err, res) => {
                if (err) return done(err)
                expect(res.body && typeof res.body === 'object').toBe(true)
                expect(res.body).toHaveProperty('data')
                expect(res.body.data).toHaveProperty('user')
                expect(res.body.data.user).toHaveProperty('id')
                expect(res.body.data.user.id).toBe(userId)

                expect(res.body.data.user).toHaveProperty('friends')
                expect(res.body.data.user.friends).toContainEqual({ id: '1' })
                return done()
            })
    })
})
Enter fullscreen mode Exit fullscreen mode

BONUS: Running tests in the CI/CD with Github Actions

Finally, a good practice is to run your tests in the CI/CD to make sure you don't deploy broken code to production. Github Actions are a quick and easy way to run your tests every time your push a new commit, and will break your pipeline if not all the tests are green ✅

name: Run tests
on: [push]
jobs:
    tests:
        name: Run tests
        runs-on: ubuntu-latest
        steps:
            - name: Checkout
              uses: actions/checkout@v2

            - name: Install dependencies
              run: npm install
                        - name: Schema linter
                            run: npm run graphql:lint

            - name: Run tests
              run: npm run test
Enter fullscreen mode Exit fullscreen mode

Conclusion

Let’s recap.

We saw why testing your app is not an option, and how you can test your GraphQL API:

  1. test your schema with eslint-plugin-prettier and schema-graphql-linter,
  2. test your operations with EasyGraphQL Tester
  3. test your resolvers like you would with any standard function,
  4. test end-to-end using supertest.

And if you want an in-depth security check of your GraphQL endpoint, head over to graphql.security to run a dozen of security scans. Completely free of charge, no login required.

Top comments (0)