Мокирование API: MSW, моки Axios и GraphQL
↑ 5.7 Тестирование приложения · ← Предыдущая · → Следующая
Зачем это на собесе
MSW — стандарт мокирования сети: тест видит настоящий HTTP-слой, а не подмену модулей.
Объяснение
MSW (Mock Service Worker) перехватывает сетевые запросы на уровне сети: в браузере через Service Worker, в тестах (Node) — через msw/node (интерсепторы). Код приложения (Axios/fetch/Vue Query) работает как в реальности.
// test/mocks/handlers.ts
import { http, HttpResponse, graphql, delay } from "msw";
export const handlers = [
http.get("/api/orders", ({ request }) => {
const page = Number(new URL(request.url).searchParams.get("page") ?? 1);
return HttpResponse.json({ items: makeOrders(20), total: 100, page });
}),
http.post("/api/orders", async ({ request }) => HttpResponse.json({ id: 1, ...(await request.json()) as object }, { status: 201 })),
http.get("/api/orders/:id", ({ params }) => params.id === "404" ? new HttpResponse(null, { status: 404 }) : HttpResponse.json(makeOrder(Number(params.id)))),
graphql.query("Orders", () => HttpResponse.json({ data: { orders: [] } })),
];
// test/setup.ts
import { setupServer } from "msw/node";
export const server = setupServer(...handlers);
beforeAll(() => server.listen({ onUnhandledRequest: "error" })); // непредусмотренные запросы — ошибка
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
// переопределение в тесте
it("показывает ошибку при 500", async () => {
server.use(http.get("/api/orders", () => new HttpResponse(null, { status: 500 })));
render(OrdersPage, { global: { plugins: [queryPlugin] } });
expect(await screen.findByText(/не удалось загрузить/i)).toBeInTheDocument();
});
server.use(http.get("/api/orders", async () => { await delay(200); return HttpResponse.json(...) })); // задержки, состояния загрузкиСравнение подходов:
| Подход | Плюсы | Минусы |
|---|---|---|
vi.mock("axios") / мок API-модуля | быстро | тест связан с реализацией, не проверяет заголовки/URL/сериализацию |
| MSW | реалистично, не зависит от HTTP-клиента, переиспользуется в dev/Storybook | нужна настройка |
| Реальный тестовый стенд | максимальная реальность | медленно и нестабильно |
| Prism/mock-сервер по OpenAPI | контракт | внешний процесс |
Практика: общие моки для тестов, Storybook и режима разработки (worker.start()); данные — фабрики (@faker-js/faker, @mswjs/data); onUnhandledRequest: "error"; валидация запросов схемой OpenAPI.
Мок Axios: axios-mock-adapter (подходит для простых случаев), но MSW независим от клиента.
Нюансы и подводные камни
- Vue Query кэширует между тестами: создавайте новый
QueryClientна тест (retry: false). - Относительные URL в Node-окружении требуют базового адреса (
jsdom+location). - Забытый
resetHandlersпротекает между тестами. - Асинхронность: используйте
findBy…иwaitFor. - Не мокайте то, что тестируете (собственный API-слой).
Практика
- Настройте MSW для Vitest и протестируйте загрузку списка и ошибку 500.
- Используйте те же handlers в Storybook/dev.
- Проверьте, что запрос содержит нужные заголовки и тело.
Вопросы с ответами
Чем MSW лучше
vi.mock("axios")?Тест не привязан к клиенту и проверяет реальный HTTP-слой (URL, заголовки, сериализацию), моки переиспользуются в разных окружениях.
Зачем
onUnhandledRequest: "error"?Чтобы тест падал при запросах, для которых нет моков, а не уходил в сеть.