Более простой и понятный способ имитировать данные 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