Схема, изображающая процесс взаимодействия между Frontend, API, Backend и Mock Service Worker. Frontend соединяется с API стрелкой, направленной вправо. От API идёт стрелка вправо с вопросом "Моки есть? А если найду?", после чего соединяется с Backend и Mock Service Worker, каждая из которых представлена отдельным блоком. Все блоки имеют жёлтый цвет и прямоугольную форму.

Имитация работы с сетью через Mock Service Worker


Последнее обновление:

В этой статье я расскажу о том, что такое Mock Service Worker и для чего его использовать. На личном опыте расскажу, как MSW сэкономит время и нервы разработчика. В конце статьи сделаю шаблон, как настроить Mock Service Worker во Vue 3 приложении с использованием faker.js.

P.S.: Про использование MSW в тестировании выйдет отдельная статья.

Что такое Mock Service Worker?

Mock Service Worker (MSW) — это библиотека для создания и управления моками API запросов в браузере и Node.js приложениях. Библиотека разработана таким образом, чтобы не зависеть от JavaScript фреймворков и любых библиотек, которые используют для работы с сетью (axios и аналоги). MSW использует Service Worker API для перехвата сетевых запросов на уровне сети, что позволяет ему максимально точно имитировать поведение реальных запросов. “Из коробки” предоставляется файл сервис-воркера, который подключается и регистрируется на этапе создания приложения. В этом же файле и идёт прослушка всех HTTP-запросов.

Рабочие кейсы с использованием Mock Service Worker.

  1. Параллельная разработка с backend-разработчиком. На моём личном опыте, зачастую разработка идёт таким образом, что задачу параллельно берут два разработчика: один с клиентской стороны, а второй с серверной. В таком случае прописывается контракт, по которому будет идти взаимодействие между сервером и клиентом, благодаря которому и организуются mock-запросы.
  2. В случае отсутствия доступа к stage-серверу. В компаниях часто было такое, что во время разработки есть всего один stage и несколько разработчиков, которые могут его положить, а локально ставить backend не всегда является лёгкой задачей. В таком случае MSW помогал не тратить время и продолжать разработку не обращая внимания на реальный stage.
  3. MSW в design review. Как правило, дизайнеру без разницы, как работает сервер. Но, если во время проверки задачи дизайнер не имеет возможности взаимодействовать с тестовым окружением - задача будет висеть, пока кто-то из backend-разработчиков не исправит ошибку на stage. Включение моков на нужные запросы для stage-окружения помогает избежать такой проблемы и позволит дизайнеру проверить вёрстку.
  4. Воспроизведение багов. Бывают случаи, когда баг известен, но у вас не получается его локализовать по каким-то причинам. На личной практике несколько раз были случаи, когда баг был многосоставной и зависел сразу от нескольких сетевых запросов на сервер и только благодаря логам ответов от сервера и мокам удавалось воспроизвести его.

Как настроить Mock Service Worker во Vue 3?

  1. Создаём демо-проект на Vue 3 командой в консоли. Следуем всем инструкциям и выбираем нужные нам пункты, я выбрал только TypeScript.
pnpm create vite
  1. Ставим необходимые библиотеки для работы mock-сервера.
pnpm install @faker-js/faker @mswjs/data msw --save-dev
  1. Инициализируем service worker, который дальше будет регистрироваться в браузере. В этот файл лезть руками не понадобится.
npx msw init ./public --save
  1. В своём проекте я использовал JSON Placeholder и настраивать моки я буду под него. Для этого создадим следующую файловую структуру:
📂 mocks/
├── 📁 data/
│   ├── 📄 createPost.ts
│   └── 📄 getPosts.ts
├── 📁 handlers/
│   ├── 📄 createPost.ts
│   ├── 📄 getPosts.ts
│   └── 📄 index.ts
├── 📄 index.ts
└── 📄 settings.ts
  1. Создаём функции генерации данных на faker.

В запросах, где нет особых требований к конечным данным из моков, можно использовать faker.js для их генерации. В примере также есть mock-функция, которая имитирует получение userId из localStorage. В production вместо localStorage могут быть как query-параметры запроса, так и cookie.

// mocks/data/getPosts.ts

export const getPosts = (limit: number = 15, userId?: number): IPost[] => {
  const mockData = [];

  for (let i = 1; i <= limit; i++) {
    mockData.push({
      userId: userId ?? faker.number.int({ min: 1, max: 100 }),
      id: i,
      title: faker.lorem.words(),
      body: faker.lorem.paragraph(),
    });
  }

  return mockData;
};
// mocks/data/createPost.ts

import type { IPost } from "@/types/Posts";

interface IRequestBody {
  title: string;
  body: string;
  userId: number;
}

export const createPost = (body: IRequestBody): IPost => ({
  title: body.title,
  body: body.body,
  userId: body.userId,
  id: 101,
});
  1. Пишем перехватчики запросов.
// mocks/handlers/getPosts.ts

import { HttpResponse } from "msw";
import { getPosts } from "../data/getPosts";

const getUserId = (): number | null => {
  return localStorage.getItem("userId")
    ? Number(localStorage.getItem("userId"))
    : null;
};

export function getPostsHandler({ request }) {
  const searchParams = new URLSearchParams(new URL(request.url).search);
  const limit = Number(searchParams.get("limit"));

  return HttpResponse.json(getPosts(limit, getUserId()), {
    status: 200,
  });
}

На этом примере я показал, как можно использовать body самого запроса для генерации нужного mock-ответа.

// mocks/handlers/createPost.ts

import { HttpResponse } from "msw";
import { createPost } from "../data/createPost";

export async function createPostHandler({ request }) {
  const requestBody = await request.json();

  return HttpResponse.json(createPost(requestBody), {
    status: 200,
  });
}
  1. Пишем нужные нам настройки для mock-сервера. В моём случае это всего лишь список url-адресов. Указывать возможные query-параметры запросов тут не нужно - сам MSW при перехвате запроса не обращает на них внимания.
// mocks/settings.ts

const host = "https://jsonplaceholder.typicode.com";

export const urls = {
  getPosts: `${host}/posts`,
  createPost: `${host}/posts`,
};
  1. Возвращаемся в handlers/index.ts и регистрируем перехватчики запросов.
import { http } from "msw";
import { urls } from "@/mocks/settings";

import { createPostHandler } from "./createPost";
import { getPostsHandler } from "./getPosts";

export const handlers = [
  http.get(urls.getPosts, getPostsHandler),
  http.post(urls.createPost, createPostHandler),
];
  1. Инициализируем service worker в проекте.
// mocks/index.ts

import { setupWorker } from "msw/browser";
import { handlers } from "@/mocks/handlers";

export const worker = setupWorker(...handlers);

В самом main-файле приложения документация MSW рекомендует использовать promise для управления включением service worker-а.

// main.ts

import { createApp } from "vue";
import App from "./App.vue";

const prepare = async () => {
  if (!isProduction()) {
    const { worker } = await import("./mocks");
    return worker.start();
  }

  return;
};

const app = createApp(App);

prepare().then(() => {
  app.mount("#app");
});

В примере для включения MSW используется лишь один флаг - isProduction. В production проекте можно создать дополнительный компонент с кнопкой включения и показывать этот компонент только на тестовых окружениях, чтобы любые разработчики могли включать и отключать mock service worker самостоятельно.

  1. Service Worker может не работать на localhost в некоторых браузерах без SSL сертификата, поэтому возможно на этом этапе у вас не будет работать MSW в стандартном режиме, и в этом случае вам нужно обратить внимание на документацию, где есть ответ, как это исправить.

Заключение

Мой рецепт был проверен на Vue 3 в демо-проекте, использовался на работе во Vue 2 и легко адаптируется под React. Теперь вы можете, используя этот базовый рецепт и адаптировать его под свой проект.

Ссылки

© 2024 Рассудихин Алекс