Более простой и понятный способ имитировать данные GraphQL.

Вступление

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

Изолированная сборка означает, что нам не нужно ждать бэкэнд-реализации, чтобы написать клиентский код. Мы можем изменять дизайн и функции и позволять клиенту диктовать наши требования к серверной части. Разработка, управляемая клиентом, отлично сочетается со строго типизированной природой GraphQL, а возможность мгновенно имитировать новые поля / запросы в вашем клиенте с помощью всего лишь пары строк кода является невероятно мощной.

В этой статье я покажу технику использования плагина Webpack NormalModuleReplacement для замены модуля Apollo 2 Schema Link; использование graphql-tools имитирующих функций. Хотя в приведенных здесь примерах будет использоваться React, тот же метод должен работать с Vue, Angular и другими библиотеками.

Мы начнем с установки Apollo Client и настройки его для использования graph.cool Star Wars API. Если у вас уже есть опыт работы с клиентом Apollo 2, не стесняйтесь переходить к разделу насмешек.

Настройка клиента Apollo

Во-первых, давайте установим наши зависимости:

npm install --save-dev apollo-client apollo-cache-inmemory apollo-link-http apollo-link-schema graphql-tools graphql-tag react-apollo

Затем мы создадим HTTP-ссылку для связи с нашим сервером. Вы можете думать о Apollo Link как о компонуемом промежуточном программном обеспечении, которое позволяет нам перехватывать, манипулировать и выполнять операцию GraphQL:

// src/graphql/http-link.js
import { HttpLink } from 'apollo-link-http';
export default new HttpLink({
  uri: 'https://swapi.graph.cool',
});

Теперь давайте создадим нашего клиента Apollo. Клиент управляет нашим кешем и выборкой данных и предоставляет API для управления этим поведением:

// src/graphql/apollo-client.js
import { ApolloClient } from 'apollo-client';
import { InMemoryCache } from 'apollo-cache-inmemory';
import link from './http-link';
export default new ApolloClient({
  link,
  cache: new InMemoryCache(),
});

Наконец, мы создадим нашего провайдера, используя модуль интеграции уровня представленияreact-apollo:

// src/components/root.jsx
import React from 'react';
import { ApolloProvider } from 'react-apollo';
import AppComponent from './app';
import client from '../graphql/apollo-client';
const RootComponent = () => (
  <ApolloProvider client={client}>
    <AppComponent />
  </ApolloProvider>
);
export default RootComponent;

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

// src/components/app.jsx
import React from 'react';
import { graphql } from 'react-apollo';
import FilmsQuery from '../graphql/queries/sw-films.graphql';
const AppComponent = ({ data: { allFilms, error, isLoading } }) => (
  /* display content here */
);
export default graphql(FilmsQuery)(AppComponent);

В приведенном выше коде предполагается использование загрузчика веб-пакетов GraphQL, доступного в модуле graphql-tag. Возможный запрос может выглядеть так:

query StarWarsFilms {
  allFilms {
    title
    id
  }
}

На этом этапе у нас должен быть интегрированный клиент GraphQL.

Издевательство

Теперь, когда у нас есть клиент Apollo, настроенный и получающий данные для нашего приложения, давайте настроимся на имитацию результатов запроса. Если вы пропустили эту часть, одно важное замечание из предыдущей настройки заключается в том, что мы экспортируем наш экземпляр Http Link в отдельный модуль из нашего клиента Apollo. Вскоре вы увидите важность этого разделения.

Сначала мы добавим нашу схему. Мы можем вручную написать быструю схему, если мы работаем до разработки сервера. В качестве альтернативы наш клиент может получить его с существующего сервера, используя запрос самоанализа.

Во время написания этого руководства я наткнулся на graphql-cli, который отлично подходит для получения удаленной схемы и, возможно, я буду использовать его в будущих проектах. Чтобы использовать cli, просто выполните следующие команды, используя URI в вашей Http-ссылке, когда будет предложено указать конечную точку:

npm install -g graphql-cli
graphql init
graphql get-schema

В результате мы получим .graphqlconfig и schema.graphql, которые мы добавим к нашему источнику.

Следующий шаг не является обязательным, но я покажу, как мы можем указать значения для частей нашего макетного графика. Это может быть полезно при интеграционном тестировании, когда вы можете подтвердить наличие определенных данных в представлении:

// src/graphql/mock-data/sw-films.js
export default [
  { title: 'A New Hope' },
  { title: 'The Empire Strikes Back' },
  { title: 'Return of the Jedi' },
];

Теперь давайте создадим ссылку на схему, которую мы будем использовать для предоставления фиктивных данных:

// src/graphql/schema-link.js
import {
  makeExecutableSchema,
  addMockFunctionsToSchema,
} from 'graphql-tools';
import { SchemaLink } from 'apollo-link-schema';
import films from './mock-data/sw-films';
import typeDefs from './schema.graphql';
const schema = makeExecutableSchema({ typeDefs });
const mocks = {
  Query: () => ({
    allFilms: () => films,
  }),
};
addMockFunctionsToSchema({ mocks, schema });
export default new SchemaLink({ schema });

Важным моментом здесь является то, что addMockFunctionsToSchema будет предоставлять значения, соответствующие типу, для полей запроса, которые мы не разрешили. В результате мы получим действительный UUID для каждого поля id фильма, даже если он не существует в наших фиктивных данных. Это позволяет нам выбрать, какие части графика важно явно имитировать, оставляя graphql-tools для заполнения остальных.

Конфигурация Webpack

В нынешнем виде, если вы замените Http Link на Schema Link в вашем Apollo Client, вы сможете запускать свое приложение, используя фиктивные данные. Это здорово, но мы не хотим вносить изменения в исходный код, чтобы переключаться между фиктивными и реальными данными.

Вместо этого мы будем использовать NormalModuleReplacement webpack для замены в нашем модуле ссылок на основе переменной среды, которую мы установим в нашем package.json.

Но сначала конфиг нашего webpack (версия 4):

// webpack.conf.js
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require('path');
module.exports = (env) => {
  const config = {
    devtool: 'eval-source-map',
    devServer: {
      contentBase: path.join(__dirname, 'dist'),
      compress: true,
      port: 3333,
      disableHostCheck: true,
    },
    module: {
      rules: [
        {
          exclude: /node_modules/,
          test: /\.(js|jsx)$/,
          use: {
            loader: 'babel-loader',
          },
        },
        {
          exclude: /node_modules/,
          test: /\.(graphql|gql)$/,
          loader: 'graphql-tag/loader',
        },
      ],
    },
    resolve: {
      extensions: ['.js', '.jsx'],
    },
    plugins: [
      new HtmlWebpackPlugin({
        template: './src/index.html',
      }),
    ],
  };
  if (env && env.GRAPHQL_MOCK) {
    config.plugins.push(new webpack.NormalModuleReplacementPlugin(
      /graphql\/http-link\.js/,
      './schema-link.js'
    ));
  }
  return config;
};

Несколько примечаний здесь:

  • Webpack 4 вышел в день написания этой статьи, и все вышеперечисленное в основном представляет собой слегка измененную конфигурацию v3 и, вероятно, не лучший порт. Я обновлю и удалю этот комментарий, когда он будет меньше отстой.
  • Мы экспортируем функцию, которая принимает env в качестве аргумента. Если ваша предыдущая конфигурация экспортировала объект, вам потребуется небольшой рефакторинг.
  • Особая магия макета происходит в блоке кода if (env && env.GRAPHQL_MOCK). Мы говорим webpack поменять наш http-link модуль на schema-link в зависимости от наличия env.GRAPHQL_MOCK.

И, наконец, мы обновляем наш package.json, чтобы установить env, когда мы хотим использовать фиктивные данные (обратите внимание, что флаг --mode является новым для Webpack v4):

"scripts": {
    "start": "webpack-dev-server --open --mode development",
    "start:mock": "webpack-dev-server --open --mode development 
        --env.GRAPHQL_MOCK",
}

Теперь мы можем легко переключаться между нашими реальными данными и нашими фиктивными данными с помощью:

npm start

А также:

npm run start:mock

Заключение

Надеюсь, эта статья оказалась для вас полезной. Здесь нет ничего особенно нового (есть документация по каждому из ключевых компонентов), но я никогда не находил ресурса, объединяющего все это таким образом.

Я также не обиделся бы, если бы мне сказали, что любой интеграционный тест, предполагающий замену одного из модулей вашего приложения, не является настоящим интеграционным тестом. Однако я думаю, что гибкость и простота этого подхода стоит такого маржинального риска. И любые проблемы с Http Link должны быть обнаружены в ходе вашего сквозного тестирования.

Другая разумная критика этого метода заключается в том, что при просмотре кода может быть не сразу очевидно, что Http Link может быть заменен местами во время сборки. Я согласен с этим и предлагаю добавить комментарий и упоминание в файле readme для вашего приложения.

И наконец, как и было обещано, пример репозитория: https://github.com/garyaparker/apollo-mocking