Моки без лишней мороки с mswjs+faker.js

Kate

Administrator
Команда форума
Меня зовут Виктор, я фронтенд-работчик в Admitad. Моя команда делает личный кабинет клиентов. Недавно я в очередной раз столкнулся с типичной проблемой: для создания нового функционала фронтенд и бэкенд нужно было реализовывать параллельно. Но как делать фронт, не имея 100% рабочих эндпойнтов на бэкенде? Сегодня я расскажу о том, какие подходы применял, и разберу их плюсы и минусы.

О проблеме​

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

Mock-объект - тип объектов, реализующих заданные аспекты моделируемого программного окружения (c)Википедия

Как организовать получение mock-данных?​

Для решения проблемы есть несколько вариантов. Начнем с самого популярного.

Пишем сервер на node.js​

Мы всегда можем написать свой mock-server на node.js используя такие фреймворки, как express или koa.

Особых сложностей в этом нет, особенно, если вы уже знакомы с node.js. Например, на express код простейшего сервера будет выглядеть таким образом:

import express from 'express';

const app = express();

const port = 3000;

app.get('/', (req, res) => {
res.send('This is Main!');
});

app.listen(port, () => {
console.log(`App listening at http://localhost:${port}`);
});
О более продвинутом можно почитать тут.

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

Используем внешние API сервисы​

Уверен, многие знакомы с таким сервисом готовых API, как jsonplaceholder.typicode.com, в котором есть как готовые энднпойнты, например, /posts, /comments..., так и возможность добавлять свои, причем, либо создав в репозитории своего проекта файл db.json с фейковыми данными, либо с помощью приложения Mockend. Главный, на мой взгляд, минус заключается в том, что такой подход не позволяет использовать например POST-запросы.

Используем Mock Service Worker + Faker​

Этот способ мне показался наиболее удобным, поскольку позволяет вести разработку в пределах основного проекта, легко подключая необходимые эндпойнты и отключая уже ненужные. В отличие от предыдущего способа, он предоставляет такие возможности как обработка всех REST-запросов и генерация набора данных с помощью моделей.

Итак, наши инструменты:

  • Mock Service Worker(далее MSW) - перехватывает запросы на сетевом уровне в браузере, обрабатывает их и возвращает ответы;
  • Faker - генерирует данные в соответствии с нашими потребностями;
Работает всё следующим образом: настраиваем service worker, который будет обрабатывать наши сетевые запросы и возвращать сгенерированные нами фэйковые данные, пока бэкенд в разработке. Таким образом работа двух команд может вестись параллельно. А когда бэкенд будет готов, достаточно будет сделать сборку без флага, запускающего mock-сервис.

Две основные концепции с которыми работает MSW, это:

  • request handler - обработчик запроса, который определяет, должен ли быть замокан запрос
  • response resolver - функция, принимающая перехваченный запрос и возвращающая замоканный ответ

Обработчики запросов​

MSW содержит в себе два набора методов для создания обработчиков для работы с API: rest и graphql.

Методы rest:

  • rest.get()
  • rest.post()
  • rest.put()
  • rest.patch()
  • rest.delete()
  • rest.options()
Первым аргументом методы принимают URL запроса, а вторым response resolver.

Пример из документации:

import { setupWorker, rest } from 'msw'

const worker = setupWorker(
rest.get('/users/:userId', (req, res, ctx) => {
const { userId } = req.params

return res(
ctx.json({
id: userId,
firstName: 'John',
lastName: 'Maverick',
}),
)
}),
)

worker.start()

Пример​

Я создал простой проект с использованием mswjs + faker в качестве mock-сервера. Код проекта находится в репозитории. Фронт написан на React+TypeScript, redux в качестве стейт менеджера в связке с redux-saga, для стилей Sass. Файловая структура такая:

acad513797a78b0b8f3448297ebd7357.png

Пройдемся по папкам:

  • в api хранится реализация отправки сетевых запросов
  • в components понятно, компоненты
  • в mocks все, что связано с mock-сервером
  • в modules вынесен функционал связанный с каждой отдельной сущностью, в моем случае это Students
  • в store находится все, что отвечает за состояние приложения
Для создания проекта я использовал create-react-app:

npx create-react-app studentList
Далее установил faker и Mock Service Worker как devDependencies:

Настройки MSW​

Для начала инициализируем Mock Service Worker используя следующую команду:

npx msw init public/ --save
В результате в папке /public будет создан файл mockServiceWorker.js со всеми необходимыми настройками для подключения сервис-воркера для mock-сервера.

После чего создадим файл src/mocks/browser.js, в котором подключим обработчики запросов:

// src/mocks/browser.js

import { setupWorker } from 'msw';
import { handlers } from './handlers';

export const worker = setupWorker(...handlers);
Внимание! Использовать Mock Service Worker в production сборке крайне не рекомендуется!

Используем переменную окружения NODE_ENV, для проверки окружения, для которого собирается приложение в src/index.js:

// src/index.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

if (process.env.NODE_ENV === 'development') {
const { worker } = require('./mocks/browser');

worker.start();
}

ReactDOM.render(<App />, document.getElementById('root'));
После выполнения команды npm start и запуска приложения откроем браузер и увидим в консоли Developer Tools сообщение о том, что mock-server запущен.

2e8999eec24263734f354bc739f753b0.png

Значит все правильно настроили.

Подводные камни​

Описанный пример взят из документации, но с ним все не так просто. Если приложение сразу после загрузки отправляет сетевые запросы (а именно так и работает наше приложение), вначале посыпятся 404-е ответы. Так происходит, потому что регистрация сервис-воркера - процесс асинхронный. Поэтому старт приложения и необходимые для этого запросы необходимо выполнить после окончания этого процесса:

Добавляем обработчики запросов​

Список студентов я получаю по запросу GET /students. Создадим для него обработчик:

// /src/mocks/handlers/students.ts

import {rest} from 'msw';

import { STUDENTS_LIST_ROUTE_MASK } from '../mocks.constants';
import {db} from '../db';
import { IStudent } from '../../components/StudentCard/StudentCard.types';
export const getStudentsHandler = rest.get(STUDENTS_LIST_ROUTE_MASK, ( req, res, ctx)=>{
// Генерируем мо модели массив из шести студентов
// и возвращаем его в ответе
let students:IStudent[] = [];
for (let i = 0; i &lt; 6; i += 1) {
students.push(db.student.create());
}

return res(
ctx.json({
students
})
)

})

Модель данных​

Для генерации списка студентов используем библиотеку для моделирования данных @mswjs/data и faker.

// /src/mocks/models/StudentModel

import { primaryKey } from '@mswjs/data';
import faker from 'faker';

export const StudentModel = {
id: primaryKey(() => faker.random.number(10000).toString()),
firstName: ()=> faker.name.firstName(),
lastName: () => faker.name.lastName(),
age: () => faker.datatype.number({min: 18, max: 69}),
email: () => faker.internet.email(),
phone: () => faker.phone.phoneNumber(('+7 (###) ###-##-##')),
city: () => faker.address.cityName(),
company: () => faker.company.companyName(),
avatar: () => faker.image.avatar(),
information: () => faker.lorem.words(10),
};
В каждой модели прописываем данные, которые faker будет генерировать на каждый запрос. Апишка фейкера выглядит немного необычно, но довольно удобно, это очень дружелюбный DSL, реализованный с помощью цепочек выполнения, разделенный по категориям, которых более 20 (!). Используем следующие категории:

Image

Содержит в себе методы для генерации изображений. Например, для того чтобы сгенерировать URL, содержащий аватар 128x128 пикселей нужно вызывать

faker.image.avatar()

Таким образом мы получим URL, например:


Company

Возвращает информацию связанную с компанией, это может быть: companySuffix, catchPhrase, или множество других полей, нам понадобится метод companyName.

Internet

Тут нам понадобятся методы email, url. Названия говорят сами за себя.

Datatype

Генерирует случайную последовательность таких типов, как:

  • number - целые числа, предел которых можно ограничить полями min и max
  • float - действительные числа
  • arrayElements - массив
  • и т.д.
В файле db.ts для подключения созданной модели используем функцию factory из @mswjs/data:

// /src/mocks/db.ts

import { factory } from '@mswjs/data';

import {StudentModel} from './models'

export const db = factory({
student: StudentModel
})
Запустив приложение, в браузере увидим сгенерированный список студентов:

Пример отображения данных, сгенерированных с помощью faker.js и полученных приложением по запросу GET /students
Пример отображения данных, сгенерированных с помощью faker.js и полученных приложением по запросу GET /students
По каждому запросу в консоли можно увидеть подробную информацию от MSW:

e6970beb915964938865118001b60d3a.png

Для того, чтобы сервис-воркер перестал перехватывать тот или иной запрос, можно просто отключить/удалить соответствущий обработчик.

Вывод​

Описанный подход существенно упрощает и ускоряет разработку, поскольку делает фронтенд-команду менее зависимой от бэкенда. Основными преимуществами в таком походе для меня стали:

  • возможность запуска mock-сервера непосредственно из основного проекта
  • легкое переключение между реальными и фейковыми эндпойнтами
  • быстрая и гибкая настройка обработчиков запросов

 
Сверху