Как смоделировать свой 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.js:

Так что же на выходе? Выполнение того же запроса, что и раньше, дает:

{
  "data": {
    "author": {
      "firstName": "Hello World",
      "posts": [
        {
          "title": "Hello World",
          "votes": -70
        },
        {
          "title": "Hello World",
          "votes": -77
        }
      ]
    }
  }
}

Ну это глупо. Каждая строка - «Hello World», голоса отрицательные, и на каждого автора всегда будет ровно два сообщения. Мы это исправим, но сначала ...

Зачем использовать моки?

Моки часто используются в модульных тестах, чтобы отделить тестируемую функциональность от зависимостей, от которых зависят эти функции. Вы хотите протестировать функцию (устройство), а не целый комплекс функций.

На этой ранней стадии разработки макеты служат другой цели: тестировать тесты. В базовом тесте вы хотите сначала убедиться, что тест правильно вызывает API и что возвращаемые результаты имеют ожидаемую структуру, свойства и типы. Думаю, крутые ребята называют это «формой».

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

Реалистичное издевательство

Мне очень нравится модуль под названием casual. Он предоставляет разумные и переменные значения для многих распространенных типов данных. Если вы демонстрируете свой новый API перед измученными коллегами, похоже, что вы сделали что-то особенное.

Вот список желаний для отображения фиктивных значений:

  1. Имя автора должно быть похоже на имя.
  2. Заголовки сообщений должны представлять собой переменный lorem ipsum текст ограниченной длины.
  3. голоса должны быть положительными или нулевыми
  4. количество постов должно варьироваться от 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 для ознакомления.

Помогите мне, если вы нашли эту статью информативной.