Я создал этот шаблон, чтобы упростить запуск нового проекта GraphQL с помощью DataLoader - https://github.com/entria/graphql-dataloader-boilerplate

По мере роста нашей базы исходного кода нам нужно все больше и больше тестов, чтобы убедиться, что мы ничего не сломаем при добавлении новых функций и проведении некоторого рефакторинга. Итак, как я могу протестировать сервер GraphQL, который использует мангуста под капотом?

Я просмотрел фреймворки тестирования и понял, что Jest - лучший вариант, доступный прямо сейчас, его система фиксации - отличная идея, и у них также есть действительно классная функция, называемая тестированием снимков.

Для начала добавим шутку в наш проект devDependencies:

yarn add jest jest-cli --dev

Вы также должны добавить это в свой package.json:

"jest": {
  "testEnvironment": "node",
  "testPathIgnorePatterns": [
    "/node_modules/",
    "./dist"
  ],
  "coverageReporters": [
    "lcov",
    "html"
  ],
  "moduleNameMapper": {
    "^mongoose$": "<rootDir>/node_modules/mongoose"
  },
},

Итак, перейдем к важной части, части тестирования GraphQL.

Хочу показать, как тестировать UserType:

export default new GraphQLObjectType({
  name: 'User',
  description: 'Object with data related and only available to the logged user',
  fields: () => ({
    id: globalIdField('User'),
    _id: {
      type: GraphQLString,
      resolve: user => user._id,
    },
    name: {
      type: GraphQLString,
      resolve: user => user.name,
    },
    email: {
      type: GraphQLString,
      resolve: user => user.email,
    },
    active: {
      type: GraphQLBoolean,
      resolve: user => user.active,
    },
  }),
  interfaces: () => [NodeInterface],
});

Поле me ViewerType будет иметь значение NULL, если пользователь не вошел в систему, и вернет текущего пользователя, если токен является действительным.

Тест прост и выглядит следующим образом:

import { graphql } from 'graphql';
import { schema } from '../../schema';
import {
  User,
} from '../../models';
import {
  getContext,
  setupTest,
} from '../../../test/helper';

beforeEach(async () => await setupTest());
it('should be null when user is not logged in', async () => {
  //language=GraphQL
  const query = `
    query Q {
      viewer {
        me {
          name
        }
      }
    }
  `;

  const rootValue = {};
  const context = getContext();

  const result = await graphql(schema, query, rootValue, context);
  const { data } = result;

  expect(data.viewer.me).toBe(null);
});

it('should return the current user when user is logged in', async () => {
  const user = new User({
    name: 'user',
    email: '[email protected]',
  });
  await user.save();
  //language=GraphQL
  const query = `
    query Q {
      viewer {
        me {
          name        
        }
      }
    }
  `;

  const rootValue = {};
  const context = getContext({ user });

  const result = await graphql(schema, query, rootValue, context);
  const { data } = result;

  expect(data.viewer.me.name).toBe(user.name);
});

Редактировать - следуя идеям https://twitter.com/calebmer:

Вы также можете использовать тестирование снимков в этом случае:

expect(data.viewer.me).toBe(null); => expect(data).toMatchSnapshot();
expect(data.viewer.me.name).toBe(user.name); => expect(data).toMatchSnapshot();

Первый тест проверяет, имеет ли UserType значение NULL, когда пользователь из контекста имеет значение NULL.

Второй тест проверит, совпадает ли поле ViewerType с пользователем, вошедшим в систему (переданным в объекте контекста)

Хитрость заключается в том, чтобы подключиться к базе данных мангуста перед первым тестом и очистить базу данных для каждого теста, например:

import mongoose from 'mongoose';

import * as loaders from '../src/loader';

const { ObjectId } = mongoose.Types;

process.env.NODE_ENV = 'test';

const config = {
  db: {
    test: 'mongodb://localhost/test',
  },
  connection: null,
};

function connect() {
  return new Promise((resolve, reject) => {
    if (config.connection) {
      return resolve();
    }

    const mongoUri = 'mongodb://localhost/test';

    mongoose.Promise = Promise;

    const options = {
      server: {
        auto_reconnect: true,
        reconnectTries: Number.MAX_VALUE,
        reconnectInterval: 1000,
      },
    };

    mongoose.connect(mongoUri, options);

    config.connection = mongoose.connection;

    config.connection
      .once('open', resolve)
      .on('error', (e) => {
        if (e.message.code === 'ETIMEDOUT') {
          console.log(e);

          mongoose.connect(mongoUri, options);
        }

        console.log(e);
        reject(e);
      });
  });
}

function clearDatabase() {
  return new Promise(resolve => {
    let cont = 0;
    let max = Object.keys(mongoose.connection.collections).length;
    for (const i in mongoose.connection.collections) {
      mongoose.connection.collections[i].remove(function() {
        cont++;
        if(cont >= max) {
          resolve();
        }
      });
    }
  });
}

export async function setupTest() {
  await connect();
  await clearDatabase();
}

Вы можете добавить beforeEach в каждый тестовый файл, поскольку в Jest нет глобальной опции для этого.

У нас также есть помощник для создания нашего контекста GraphQL:

export function getContext(context) {
  const dataloaders = Object.keys(loaders).reduce((dataloaders, loaderKey) => ({
    ...dataloaders,
    [loaderKey]: loaders[loaderKey].getLoader(),
  }), {});

  return {
    ...context,
    req: {},
    dataloaders,
  };
}

он обеспечит наличие необходимых загрузчиков данных для всех наших тестов.

Вы можете следовать тому же шаблону для тестирования мутаций, соединений, интерфейсов, подписок и преобразователей.

Совет: используйте // language = GraphQL, чтобы выделить синтаксис graphql

Результат здесь: https://github.com/sibelius/graphql-dataloader-boilerplate/pull/2

Не стесняйтесь обращаться ко мне в твиттере, чтобы задать любые вопросы: https://twitter.com/sseraphini

Или просто откройте проблему, чтобы улучшить этот шаблон

Теперь мы запускаем все наши тесты параллельно, что значительно сократило время выполнения. Узнайте о новой архитектуре на этом носителе https://itnext.io/parallel-testing-a-graphql-server-with-jest-44e206f3e7d2

Новостная рассылка

Подпишитесь на мою рассылку для получения нового контента https://sibelius.substack.com/