Как смоделировать свой GraphQL API с реалистичными значениями
В моей последней статье я взял исходный Apollo LaunchPad API сообщений и авторов и разбил его на домены и компоненты. Я хотел проиллюстрировать, как можно организовать большой GraphQL-проект с помощью graphql-tools.
Теперь я хочу, чтобы API возвращал фиктивные данные, когда я их запрашиваю. Как?
Первоисточник
В исходном примере Apollo Launchpad мы использовали статические структуры данных и простые преобразователи сопоставлений для обеспечения вывода для запросов.
Например, учитывая этот запрос:
# Welcome to GraphiQL query PostsForAuthor { author(id: 1) { firstName posts { title votes } } }
Результатом будет:
{ "data": { "author": { "firstName": "Tom", "posts": [ { "title": "Introduction to GraphQL", "votes": 2 } ] } } }
У объекта resolvers есть функции, которые заботятся о сопоставлении авторов с сообщениями и наоборот. Однако это не совсем шутка.
Проблема в том, что чем больше взаимосвязей и чем сложнее становятся сущности, тем больше кода требуется для обработки преобразователей. Затем необходимо предоставить больше данных.
Когда дело доходит до тестирования, тесты могут иногда обнаруживать проблемы в данных или в преобразователях. Вам действительно нужно целенаправленное тестирование самого API.
Использование моков
Существует три модуля Node.js, которые позволяют быстро и легко смоделировать API. Первый является частью модуля graphql-tools
. При использовании этого модуля первым шагом является запрос или импорт метода addMockFunctionsToSchema
из модуля в корневой schema.js
файл:
import { makeExecutableSchema, addMockFunctionsToSchema } from 'graphql-tools';
Затем, после создания исполняемого файла schema
путем вызова createExecutableSchema
, вы добавляете свои макеты следующим образом:
addMockFunctionsToSchema({ schema: executableSchema, })
Вот полный список корневых schema.j
s:
Так что же на выходе? Выполнение того же запроса, что и раньше, дает:
{ "data": { "author": { "firstName": "Hello World", "posts": [ { "title": "Hello World", "votes": -70 }, { "title": "Hello World", "votes": -77 } ] } } }
Ну это глупо. Каждая строка - «Hello World», голоса отрицательные, и на каждого автора всегда будет ровно два сообщения. Мы это исправим, но сначала ...
Зачем использовать моки?
Моки часто используются в модульных тестах, чтобы отделить тестируемую функциональность от зависимостей, от которых зависят эти функции. Вы хотите протестировать функцию (устройство), а не целый комплекс функций.
На этой ранней стадии разработки макеты служат другой цели: тестировать тесты. В базовом тесте вы хотите сначала убедиться, что тест правильно вызывает API и что возвращаемые результаты имеют ожидаемую структуру, свойства и типы. Думаю, крутые ребята называют это «формой».
Это предлагает более ограниченное тестирование, чем запрашиваемая структура данных, потому что семантика ссылок не применяется . id
бессмысленно. Тем не менее, mocks предлагает что-то для структурирования ваших тестов вокруг
Реалистичное издевательство
Мне очень нравится модуль под названием casual. Он предоставляет разумные и переменные значения для многих распространенных типов данных. Если вы демонстрируете свой новый API перед измученными коллегами, похоже, что вы сделали что-то особенное.
Вот список желаний для отображения фиктивных значений:
- Имя автора должно быть похоже на имя.
- Заголовки сообщений должны представлять собой переменный lorem ipsum текст ограниченной длины.
- голоса должны быть положительными или нулевыми
- количество постов должно варьироваться от 1 до 7
Первым делом нужно создать папку с именем mocks
. Затем мы добавим в эту папку файл index.js
с помощью фиктивных методов. Наконец, пользовательские макеты будут добавлены в сгенерированную исполняемую схему.
Библиотека casual может генерировать значения по типу данных (String, ID, Int, …
) или по имени свойства. Кроме того, объект MockList graphql-tools будет использоваться для изменения количества элементов в списке - в данном случае posts
. Итак, начнем.
Import
как casual, так и MockList в /mocks/index.js
:
import casual from 'casual'; import { MockList } from 'graphql-tools';
Теперь давайте создадим экспорт по умолчанию со следующими свойствами:
export default { Int: () => casual.integer(0), Author: () => ({ firstName: casual.first_name, posts: () => new MockList([1, 7]) }), Post: () => ({ title: casual.title }) }
Объявление Int
заботится обо всех целочисленных типах, появляющихся в нашей схеме, и гарантирует, что Post.votes
будет положительным или нулевым.
Затем Author.firstName
будет разумным именем. MockList используется, чтобы гарантировать, что количество сообщений, связанных с каждым автором, будет от 1 до 7. Наконец, случайный будет генерировать lorem ipsum title
для каждого Post
.
Теперь сгенерированный результат меняется каждый раз при выполнении запроса. И это выглядит правдоподобно:
{ "data": { "author": { "firstName": "Eldon", "posts": [ { "title": "Voluptatum quae laudantium", "votes": 581 }, { "title": "Vero quos", "votes": 85 }, { "title": "Doloribus labore corrupti", "votes": 771 }, { "title": "Qui nulla qui", "votes": 285 } ] } } }
Создание пользовательских значений
Я лишь слегка коснулся того, на что способен казуальный стиль, но это хорошо задокументировано, и добавить особо нечего.
Однако иногда есть значения, которые должны соответствовать стандартному формату. Хочу представить еще один модуль: randexp.
randexp позволяет вам генерировать значения, соответствующие выражению регулярного выражения, которое вы ему предоставляете. Например, номера ISBN имеют формат:
/ ISBN- \ d- \ d {3} - \ d {5} - \ d /
Теперь я могу добавлять книги в схему, добавлять книги в Author и генерировать ISBN и заголовок для каждого Book
:
// book.js export default ` type Book { ISBN: String title: String }
mocks.js:
import casual from 'casual'; import RandExp from 'randexp'; import { MockList } from 'graphql-tools'; import { startCase } from 'lodash'; export default { Int: () => casual.integer(0), Author: () => ({ firstName: casual.first_name, posts: () => new MockList([1, 7]), books: () => new MockList([0, 5]) }), Post: () => ({ title: casual.title }), Book: () => ({ ISBN: new RandExp(/ISBN-\d-\d{3}-\d{5}-\d/) .gen(), title: startCase(casual.title) }) }
А вот новый запрос:
query PostsForAuthor { author(id: 1) { firstName posts { title votes } books { title ISBN } } }
Образец ответа:
{ "data": { "author": { "firstName": "Rosemarie", "posts": [ { "title": "Et ipsum quo", "votes": 248 }, { "title": "Deleniti nihil", "votes": 789 }, { "title": "Aut aut reprehenderit", "votes": 220 }, { "title": "Nesciunt debitis mollitia", "votes": 181 } ], "books": [ { "title": "Consequatur Veniam Voluptas", "ISBN": "ISBN-0-843-74186-9" }, { "title": "Totam Et Iusto", "ISBN": "ISBN-6-532-70557-3" }, { "title": "Voluptatem Est Sunt", "ISBN": "ISBN-2-323-13918-2" } ] } } }
Итак, это основы имитации с использованием инструментов graphql и пары других полезных модулей.
Примечание. В этом сообщении я использую фрагменты. Если вы хотите следовать в более широком контексте, образец кода находится здесь.
Полный исходный код находится на GitHub для ознакомления.
Помогите мне, если вы нашли эту статью информативной.