Содержание:
Имитация работы с сетью через 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.
- Параллельная разработка с backend-разработчиком. На моём личном опыте, зачастую разработка идёт таким образом, что задачу параллельно берут два разработчика: один с клиентской стороны, а второй с серверной. В таком случае прописывается контракт, по которому будет идти взаимодействие между сервером и клиентом, благодаря которому и организуются mock-запросы.
- В случае отсутствия доступа к stage-серверу. В компаниях часто было такое, что во время разработки есть всего один stage и несколько разработчиков, которые могут его положить, а локально ставить backend не всегда является лёгкой задачей. В таком случае MSW помогал не тратить время и продолжать разработку не обращая внимания на реальный stage.
- MSW в design review. Как правило, дизайнеру без разницы, как работает сервер. Но, если во время проверки задачи дизайнер не имеет возможности взаимодействовать с тестовым окружением - задача будет висеть, пока кто-то из backend-разработчиков не исправит ошибку на stage. Включение моков на нужные запросы для stage-окружения помогает избежать такой проблемы и позволит дизайнеру проверить вёрстку.
- Воспроизведение багов. Бывают случаи, когда баг известен, но у вас не получается его локализовать по каким-то причинам. На личной практике несколько раз были случаи, когда баг был многосоставной и зависел сразу от нескольких сетевых запросов на сервер и только благодаря логам ответов от сервера и мокам удавалось воспроизвести его.
Как настроить Mock Service Worker во Vue 3?
- Создаём демо-проект на Vue 3 командой в консоли. Следуем всем инструкциям и выбираем нужные нам пункты, я выбрал только TypeScript.
pnpm create vite
- Ставим необходимые библиотеки для работы mock-сервера.
pnpm install @faker-js/faker @mswjs/data msw --save-dev
- Инициализируем service worker, который дальше будет регистрироваться в браузере. В этот файл лезть руками не понадобится.
npx msw init ./public --save
- В своём проекте я использовал JSON Placeholder и настраивать моки я буду под него. Для этого создадим следующую файловую структуру:
📂 mocks/
├── 📁 data/
│ ├── 📄 createPost.ts
│ └── 📄 getPosts.ts
├── 📁 handlers/
│ ├── 📄 createPost.ts
│ ├── 📄 getPosts.ts
│ └── 📄 index.ts
├── 📄 index.ts
└── 📄 settings.ts
- Создаём функции генерации данных на 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,
});
- Пишем перехватчики запросов.
// 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,
});
}
- Пишем нужные нам настройки для mock-сервера. В моём случае это всего лишь список url-адресов. Указывать возможные query-параметры запросов тут не нужно - сам MSW при перехвате запроса не обращает на них внимания.
// mocks/settings.ts
const host = "https://jsonplaceholder.typicode.com";
export const urls = {
getPosts: `${host}/posts`,
createPost: `${host}/posts`,
};
- Возвращаемся в
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),
];
- Инициализируем 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 самостоятельно.
- Service Worker может не работать на localhost в некоторых браузерах без SSL сертификата, поэтому возможно на этом этапе у вас не будет работать MSW в стандартном режиме, и в этом случае вам нужно обратить внимание на документацию, где есть ответ, как это исправить.
Заключение
Мой рецепт был проверен на Vue 3 в демо-проекте, использовался на работе во Vue 2 и легко адаптируется под React. Теперь вы можете, используя этот базовый рецепт и адаптировать его под свой проект.