Я создал этот шаблон, чтобы упростить запуск нового проекта 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/