Эмуляция бэкенда: как разрабатывать изолированный фронтенд с помощью Mock Service Worker
Всем привет! Сегодня я хочу рассказать о Mock Service Worker — технологии, которая позволяет эмулировать поведение бэкенда в ситуациях, когда по каким-то причинам невозможно использовать реальный бэкенд для полноценной разработки фронтенда.
Эта технология подойдёт в следующих случаях:
если в вашей команде фронтенд должен разрабатываться параллельно или даже раньше, чем бэкенд на основе контрактов;
если технически невозможно использовать реальный API бэкенда в условиях локальной разработки — такая ситуация возникла в моём рабочем проекте в банке, где бэкенд функционирует только в тестовом контуре и доступен через виртуальную машину;
если вы пишите end-to-end тесты, в которых проверяете работу критических пользовательских сценариев.
Для всех выше перечисленных задач отлично подойдёт технология Mock Service Worker, которую я давно и успешно применяю в реально работающих приложениях. Тем более, что совсем недавно вышла новая мажорная версия соответствующей библиотеки msw
, и в ней достаточно много важных обновлений.
Что такое API Mocking и при чём тут Service Workers
Mock — это модель, основанная на реальных данных, а API Mocking — это технология обработки таких моделируемых данных. Функцию API Mocking сервиса может выполнять отдельный сервер на Node.js, который запускается локально и обрабатывает запросы к API. Однако существует и другой способ обработки запросов, который не требует использования отдельного сервера, при этом отлично эмулирует сетевое поведение и предоставляет мощные инструменты для тестирования, изолированной разработки и дебага различных сетевых сценариев. Это технология Mock Service Worker (далее MSW).
MSW предоставляет файл сервис-воркера, который подключается и регистрируется на этапе инициализации приложения, слушает события отправки XHR-запросов и перехватывает и обрабатывает их по заданным правилам для конкретных эндпоинтов. На продакшене Service Worker не будет активирован, и приложение автоматически переключится на работу с реальным API.
Подробнее про Service Worker API можно почитать тут: https://developer.mozilla.org/ru/docs/Web/API/Service_Worker_API.
MSW позволяет использовать привычные сервисы для отправки запросов к API (такие как встроенный fetch
, библиотеку axios
и др.) и использовать в запросах те же эндпоинты, которые работают с реальным бэкендом. Таким образом, не появляется необходимость поддерживать две конфигурации и два набора эндпоинтов для разработки и продакшена.
Как начать работать с Mock Service Worker
Для того чтобы настроить Mock Service Worker понадобится соответствующая библиотека msw
, информацию о которой можно найти на официальном сайте: https://mswjs.io.
Установка и инициализация MSW
Для того, чтобы установить библиотеку msw
, необходимо ввести следующую команду в терминале, находясь в папке проекта:
npm install msw@latest --save-dev
Для работы с последней версией библиотеки понадобится версия Node.js не меньше 18.0.0
. Если на проекте используется TypeScript, то его версию придётся поднять как минимум до версии 4.7
.
Далее необходимо инициализировать Mock Service Worker и создать необходимые файлы для его работы. Это можно сделать с помощью следующей команды:
npx msw init ./public --save
Данная команда создаст файл с воркером mockServiceWorker.js
в папке ./public
, в которой хранятся публичные статические файлы проекта. Параметр --save
позволит сохранить путь до папки в package.json
проекта для будущих обновлений скрипта воркера.
Создание обработчиков запросов
Следующий шаг — это создание обработчиков запросов, которые будут перехватывать запросы к реальному бэкенду и эмулировать его работу.
В ранних версиях msw
синтаксис работы с запросами повторял синтаксис обработчиков маршрутов на сервере Node.js, например, в Express. Однако с выпуском версии 2.0 вся логика работы была адаптирована к нативному стандарту Fetch API. Это важное изменение, позволяющее полноценно эмулировать работу с запросами с использованием самого современного браузерного стандарта.
Обработка запросов в MSW реализована через использование двух концепций:
request handler — это обработчик запросов, привязанный к конкретному URL, который перехватывает запрос и запускает функцию-резолвер;
response resolver — это функция-резолвер, которая имеет доступ к параметрам запроса и возвращает ответ, эмулирующий ответ от реального бэкенда.
Давайте рассмотрим эти концепции на практике, а заодно создадим обработчик GET-запроса на эндпоинт /api/posts
.
Для начала необходимо создать директорию src/mocks
, в которой будет храниться всё, что относится к моковым данным. Далее создадим в ней файл handlers.js
, в котором будут описаны все обработчики моковых запросов.
Начнём с импорта модуля http
, который позволяет перехватывать и обрабатывать REST-запросы:
import { http } from 'msw';
Следом напишем заготовку под обработчик:
// request handler
const postsHandler = http.get("/api/posts", postsResolver);
Модуль http
позволяет обработать как вызов конкретного стандартного REST-метода (GET, POST, PATCH и др.), так и вызовы всех методов сразу на один и тот же URL — для этого предназначен метод модуля http.all(predicate, resolver)
.
Подробнее про методы модуля http
можно почитать в документации msw
: https://mswjs.io/docs/api/http.
Кроме REST-запросов,
msw
может также эмулировать запросы к GraphQL с помощью модуляgraphql
. Дополнительную информацию об этом можно найти в документации: https://mswjs.io/docs/network-behavior/graphql.
Первый параметр в request handler называется predicate
(далее предикат) и отражает правила, по которым проверяется URL запроса. Предикат может быть как обычной строкой, так и регулярным выражением. Запрос будет обработан функцией-резолвером, переданной в параметре resolver
, только если URL запроса совпадёт с правилом, заданным в предикате. В нашем случае предикатом является строка с относительным URL api/posts
. Теперь при вызове любого GET-запроса на данный URL из клиентского приложения с работающим Mock Service Worker запустится обработчик вызова postsHandler
.
Подробнее о правилах формирования предиката можно почитать в документации: https://mswjs.io/docs/basics/intercepting-requests#http-request-matching, а пока что пойдём дальше и напишем код функции-резолвера:
// response resolver
const postsResolver = ({ request, params, cookies }) => {
return HttpResponse.json([
{
title:
"Что такое генераторы статических сайтов и почему Astro — лучший фреймворк для разработки лендингов",
url: "https://habr.com/ru/articles/779428/",
author: "@AlexGriss",
},
{
title: "Как использовать html-элемент
Response resolver предоставляет доступ к единственному аргументу в виде объекта, в котором содержится информация о перехваченном запросе.
В поле request
будет доступна реализация нативного интерфейса Request
из Fetch API, так что вам будут доступны все стандартные методы и свойства соответствующего класса. В поле params
будут доступны параметры пути, такие как, к примеру, postId
для URL вида api/posts/:postId
. Наконец, в поле cookies
будут доступны все установленные при запросе куки в виде строковых пар ключ-значение.
Функция-резолвер должна возвращать инструкцию о том, что нужно сделать при перехвате запроса. Чаще всего это будет ответ в виде mock-данных, и для этого в msw
используется инстанс нативного класса Response
из всё того же Fetch API. Вы можете использовать класс Response
напрямую, но разработчики msw
рекомендуют работать с библиотечным классом HttpResponse
, который является более продвинутой надстройкой над Response
. Например, он позволяет мокать установку cookies и дополняет стандартные методы класса Response
удобными методами для отправки ответа с различными Content-Type
.
О классе HttpResponse
можно почитать подробнее в документации библиотеки msw
: https://mswjs.io/docs/api/http-response.
Кроме обычных текстовых данных в виде plain text, json, xml или formData, библиотека
msw
позволяет возвращать стримыReadableStream
. Подробнее об этом можно почитать в соответствующем разделе документации: https://mswjs.io/docs/recipes/streaming
В приведённом примере вызывается метод HttpResponse.json()
, в который передаётся моковая структура данных — в данном случае это коллекция постов на Хабре. Далее она будет отправлена на клиент при успешной обработке вызова.
Давайте посмотрим на то, как должен выглядеть итоговый файл handlers.js
:
// src/mocks/handlers.js
import { HttpResponse, http } from "msw";
// response resolver
const postsResolver = () => {
return HttpResponse.json([
{
title:
"Что такое генераторы статических сайтов и почему Astro — лучший фреймворк для разработки лендингов",
url: "https://habr.com/ru/articles/779428/",
author: "@AlexGriss",
},
{
title: "Как использовать html-элемент
В конце необходимо эскпортировать переменную handlers
, содержащую массив со всеми обработчиками вызовов.
Настройка Mock Service Worker для работы в браузере
Далее в папке src/mocks
создадим файл browser.js
, в котором нужно будет настроить работу воркера с ранее добавленными обработчиками вызовов:
// src/mocks/browser.js
import { setupWorker } from "msw/browser";
import { handlers } from "./handlers";
export const worker = setupWorker(...handlers);
Функция setupWorker
принимает список обработчиков и подготавливает канал связи между клиентом и воркером mockServiceWorker.js
, который мы сгенерировали на первом этапе.
Далее мы готовы активировать работу MSW через вызов метода воркера worker.start()
. Ниже показан пример активации воркера во входной точке React-приложения:
// src/index.jsx
import React from 'react';
import ReactDOM from 'react-dom';
import { App } from './App';
async function enableMocking() {
if (process.env.NODE_ENV === "development") {
const { worker } = await import("./mocks/browser");
return worker.start();
}
}
const rootElement = ReactDOM.createRoot(document.getElementById("root"));
enableMocking().then(() => {
rootElement.render( );
});
Активация MSW должна происходить только в режиме разработки. Результатом выполнения метода воркера worker.start()
будет являться промис, при резолве которого необходимо рендерить клиентское приложение. Такая последовательность задач необходима для того, чтобы избежать гонки состояний между регистрацией воркера и запросами, которое делает приложение на старте работы.
Для приложения на ванильном JS достаточно просто запустить метод worker.start()
на этапе инициализации приложения.
Если всё сделано правильно, то при запуске приложения в консоли браузера можно будет увидеть сообщение:
[MSW] Mocking enabled.
MSW умеет работать не только в браузерной среде, но и в связке с Node.js и React Native. Настроить моки для данных окружений помогут соответствующие разделы в документации библиотеки: https://mswjs.io/docs/integrations/node и https://mswjs.io/docs/integrations/react-native.
Проверка обработки запросов
Теперь если в клиентском приложении сделать запрос на URL /api/posts
, Mock Service Worker должен его перехватить и вернуть mock-данные.
Давайте рассмотрим это на примере компонента Posts
в рамках React-приложения:
// src/components/Posts.jsx
import { useEffect, useState } from "react";
import { Post } from "./Post";
export const Posts = () => {
const [posts, setPosts] = useState(null);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
fetch(`/api/posts`)
.then((response) => response.json())
.then((posts) => {
setPosts(posts);
})
.finally(() => {
setIsLoading(false);
});
}, []);
return isLoading
? "Loading..."
: posts.map((post) => (
));
};
Теперь при загрузке React-компонента на вкладке Network в Developer Tools браузера отобразится XHR-запрос на URL api/posts
с ответом в виде mock-данных:
Запрос на /api/posts
MSW будет перехватывать любой запрос, если его URL соответствует предикату пути в обработчике запросов, неважно откуда из приложения он вызван. Кроме отображения во вкладке Network каждый перехваченный запрос вывыдет дополнительную информацию в консоль браузера.
В заключение
Технология Mock Service Worker является не просто средой для работы с mock-данными, она позволяет буквально эмулировать сетевое поведения без развёртывания отдельного сервера. MSW предоставляет широкие возможности для повторения логики бэкенда на клиенте, тестирования сложных пользовательских сценариев и полноценной разработки фронтенда в условиях, когда бэкенд не готов. Точная эмуляция сетевого поведения позволяет автоматически переключаться на работу с реальными данными и реальными API, как только приложение запускается в продакшене.
MSW умеет эмулировать работу как с обычными REST-запросами, так и с GraphQL и даже возвращать стримы в качестве ответа на запрос. Библиотека msw
позволяет настроить интеграцию сервис-воркера с различными средами, такими как браузер, Node.js и React Native. Эмуляция реального сетевого поведения стала ещё более доступной в новой версии MSW с введением интерфейсов обработчиков запросов, которые повторяют реализацию нативных интерфейсов Fetch API, таких как Request
и Response
.
Мы изучили основы работы, подключения и настройки технологии Mock Service Worker, но на этом её возможности не заканчиваются. Предлагаю попробовать поэкспериментировать с MSW самостоятельно, повторив руководство, представленное в этой статье. Продолжить изучение технологии можно на сайте с документацией к библиотеке msw
: https://mswjs.io/docs/getting-started.
Приглашаю вас подписаться на мой телеграм-канал: https://t.me/alexgriss, в котором я пишу о фронтенд-разработке, публикую полезные материалы, делюсь своим профессиональным мнением и рассматриваю темы, важные для карьеры разработчика.