Unit Testing of GraphQL Middleware in TypeScript
Published on 9 October 2020This 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')
);
});
});