Unit Testing of GraphQL Middleware in TypeScript

Published on 9 October 2020

This post gives a code example on how to unit test a GraphQL middleware in TypeScript with Jest, using the Express GraphQL middleware resolver graphql-shield as an example. It should generalize to other middlewares.

Description

In this test, we essentially set up a new set of resolvers from scratch using the schema by loading all GraphQL schema files from a directory. By using makeExecutableSchema to create a schema and addMockFunctionsToSchema, we mock out the endpoints.

My application is using graphql-yoga, although this is actually irrelevant to the test environment (you’ll notice no references to it here).

In this example, I use an external Express GraphQL middleware resolver: graphql-shield, a permissions layer for GraphQL.

Drawbacks

A huge drawback of this approach is that we need to type out GraphQL queries as strings without having them typechecked. I didn’t find a good way to have typechecked queries here, although it may be possible somehow with GraphQL Code Generator (if you find a way, please comment below).

Note that it’s possible to individually unit test any rules that you use in the permissions as well (independent of GraphQL).

Code

/**
 * An example of testing a GraphQL middleware. In this test, the resolvers
 * themselves have no actual implementation.
 */
import { makeExecutableSchema, addMockFunctionsToSchema } from 'graphql-tools';
import { graphql } from 'graphql';
import { applyMiddleware } from 'graphql-middleware';
import { mergeTypeDefs } from '@graphql-tools/merge';
import { loadFilesSync } from '@graphql-tools/load-files';
import permissions from './permissions';

const SCHEMA_DIRECTORY = './src/schema';

let schemaWithMiddleware: ReturnType<typeof applyMiddleware>;

beforeAll(() => {
  const typesArray = loadFilesSync(SCHEMA_DIRECTORY, {
    extensions: ['graphql'],
  });
  const typeDefs = mergeTypeDefs(typesArray);
  const schema = makeExecutableSchema({ typeDefs });
  addMockFunctionsToSchema({ schema });
  // `permissions` here is the call shield() in GraphQL shield. Its type should
  // be compatible with applyMiddleware for graphql-middleware.
  schemaWithMiddleware = applyMiddleware(schema, permissions);
});

const Queries: Record<string, string> = {
  // An example of a GraphQL query you can write here. As mentioned in the
  // article, the inability to get static typing here is unideal.
  INITIALIZE_CARD_SESSION: `
    mutation {
      initializeCardSession(selector: { scheduleDayId: "foo" }) {
        session {
          id
        }
      }
    }
    `,
};

describe('test suite', () => {
  it('tests a single aspect', async () => {
    // Set up mocks etc here for the implementation of the middleware if needed.
    const mockContext = {};

    const result = await graphql(
      schemaWithMiddleware,
      // You can inline the query here or declare it at the top of the test if
      // you wish.
      Queries.INITIALIZE_CARD_SESSION,
      null,
      mockContext,
    );

    // You can access the result of the resolver like so.
    // Note that we are simply testing the middleware here; the actual resolver
    // has no implementation since we used addMockFunctionsToSchema().
    expect(result.data).toBeFalsy();

    // In this example test, we are testing our permissions middleware in
    // properly returning an error.
    expect(result.errors).toBeTruthy();
    expect(res.errors!.length).toBe(1);
    expect(res.errors![0]!.message).toEqual(
      expect.stringContaining('You need to be an admin')
    );
  });
});

Comments