Testing GraphQL in 3 minutes

Cole Turner
Cole Turner
3 min read
Cole Turner
Testing GraphQL in 3 minutes
Blog
 
LIKE
 
LOVE
 
WOW
 
LOL

You look outside the window and see the clouds crawling on by while you take a sip of your morning coffee and ask yourself:

“How exactly do I test my GraphQL resolvers?”

I never said I was a mind reader. But for the folks out there that are wondering that very same question, here is how you should start testing your GraphQL Server.

The first thing you want to cover is schema composition. Mocking your schema lets you write tests with real queries that test your type definitions. These tests will save you from a magnitude of potential glitches including typos and type conflicts.

Mock the schema and write test cases for every query you can imagine. Add cases for happy paths, errors, and test different contexts as well. Declaring these concerns gives us the full integration testing our Schema needs and peace of mind. Here’s how I like to do it:

import {
  makeExecutableSchema,
  addMockFunctionsToSchema,
  mockServer
} from 'graphql-tools';

const testCaseA = {
  id: 'Test case A',
  query: `
    query {
      animals {
         origin
      }
    }
  `,
  variables: { },
  context: { },
  expected: { data: { animals: [{ kind: 'Dog' }] } }
};

describe('Schema', () => {
  // Array of case types
  const cases = [testCaseA];
  
  const mockSchema = makeExecutableSchema({ typeDefs });

  // Here we specify the return payloads of mocked types
  addMockFunctionsToSchema({
    schema: mockSchema,
    mocks: {
      Boolean: () => false,
      ID: () => '1',
      Int: () => 1,
      Float: () => 12.34,
      String: () => 'Dog',
    }
  });

  test('has valid type definitions', async () => {
    expect(async () => {
      const MockServer = mockServer(typeDefs);

      await MockServer.query(`{ __schema { types { name } } }`);
    }).not.toThrow();
  });

  cases.forEach(obj => {
    const { id, query, variables, context: ctx, expected } = obj;

    test(`query: ${id}`, async () => {
      return await expect(
        graphql(mockSchema, query, null, { ctx }, variables)
      ).resolves.toEqual(expected);
    });
  });

});

In the example above we iterate through the test cases using the declared fixtures. These fixtures contain the query that will execute against our mock schema. We can specify variables, context, and the expected output from the query. This gives developers peace of mind that GraphQL can make an executable schema out of type definitions. But what about resolvers? What about integration testing beyond GraphQL?

The really neat thing about resolvers is that they’re pure functions — which makes writing tests super easy and satisfying. For a refresh, here’s what a resolver function looks like:

function animalNameResolver(obj, args, ctx, info) {
  // obj     = the parent object (Animal)
  // args    = the query field arguments
  // ctx    = shared payload for query execution
  // info    = query and field selection metadata
  return obj.name;
}

Pure functions are great for testing because there’s no internal state and no mutable payloads to worry about. Mocking the shape of these arguments we can test resolvers and add integration coverage to our test suites. This way we know our schema is valid and integrates with underlying services.

Here’s something simple to get started:

// graphql/types/Animal/Animal.test.js
import * as AnimalResolvers from './Animal.resolvers.js';

export const animalNameTest = () => {
    const obj = {
      kind: 'Dog',
      name: 'Koko',
      breeds: ['Corgi', 'German Shepherd']
    };
  
    const args = {};
    const ctx = {};

    const output = AnimalResolvers.name(obj, args, ctx);

    expect(output).toEqual('Koko');
};
// graphql/types/resolvers.test.js

import glob from 'glob';
import path from 'path';

const basePath = path.join(process.cwd(), './graphql/types/');

describe('Resolvers', () => {
  // Find all our resolver files
  const files = glob.sync(`${basePath}**/*.test.js`);
  
  files.forEach(file => {
    describe(file, () => {
      const resolvers = require(file);

      Object.entries(resolvers).forEach(([name, fn]) => {
        it(name, () => Promise.resolve(fn()));
      });
    });
  });
});

I like this pattern because I’ve found co-locating tests with your Schema to be just as advantageous to putting tests next to React components. It makes it easier to remember to write tests and helps the next developer gather the big picture. Friction when writing tests is the worst productivity killer at the cost of the testing coverage. Making tests easier to write is a no-brainer.

GraphQL Test Result - Tests Pass

Thanks for reading! GraphQL and Apollo are awesome frameworks that make building a schema in JavaScript a pleasure. If you find some other pattern that does the same for you — please feel free to share.

 
LIKE
 
LOVE
 
WOW
 
LOL

More Stories

Melt of the Day: Brisket, Muenster, & Grilled Onions

2 min read

You can't go wrong with brisket. This recipe uses Snow's BBQ, Texas Monthly's #1 Pick, and grilled onions for the perfect brisket and grilled cheese melt.

Lessons Learned in Freelancing

12 min read

If you want to become a freelance web developer, these are the lessons that will help you have a great time working with clients and being effective.

See more posts

Read it before anyone else. Subscribe to my newsletter for early access to the latest news in software engineering, web development, and more.