MSW — API mock qilish, real so'rovlarsiz test
Muammodan boshlaylik
76-darsda API ni soxtalashtirishning ikki usulini ko'rdik — ikkalasining ham kamchiligi bor:
fetchni mock qilish — Axios ga o'tsangiz, testlar buziladivi.mock('../api/users')— API modulining o'zi (URL, header lar, xatolarni qayta ishlash) tekshirilmaydi; har test faylida mock larni takrorlash
Va yana bir muammo: backend hali tayyor emas. Frontend jamoasi kutib o'tirishi kerakmi?
MSW (Mock Service Worker) so'rovlarni tarmoq darajasida ushlaydi. Sizning kodingiz — komponent, hook, fetch yoki Axios, API moduli — haqiqiy so'rov yuboradi; MSW uni server ga yetib bormasdan ushlab, soxta javob qaytaradi. Kod bu farqni sezmaydi.
Komponent → useQuery → api/users.js → fetch/axios → [MSW] ✋ → soxta javob
↑
haqiqiy server ga bormaydi
O'rnatish
npm install -D msw
Handler lar — soxta API
// src/mocks/handlers.js
import { http, HttpResponse } from 'msw';
const foydalanuvchilar = [
{ id: 1, ism: 'Ali Valiyev', email: 'ali@mail.uz' },
{ id: 2, ism: 'Zarina Karimova', email: 'zarina@mail.uz' },
];
export const handlers = [
http.get('/api/users', () => {
return HttpResponse.json(foydalanuvchilar);
}),
http.get('/api/users/:id', ({ params }) => {
const user = foydalanuvchilar.find((u) => u.id === Number(params.id));
if (!user) {
return HttpResponse.json({ xabar: 'Topilmadi' }, { status: 404 });
}
return HttpResponse.json(user);
}),
http.post('/api/users', async ({ request }) => {
const yangi = await request.json();
return HttpResponse.json({ id: 3, ...yangi }, { status: 201 });
}),
];
http.get,http.post,http.put,http.patch,http.delete— metod va URL:id— URL parametri →params.idrequest— haqiqiyRequestobyekti:request.json(),request.headers,new URL(request.url).searchParamsHttpResponse.json(malumot, { status })— JSON javob
Handler lar — Express route lariga o'xshaydi. Ular API ning kichik soxta versiyasi.
Test uchun server
// src/mocks/server.js
import { setupServer } from 'msw/node';
import { handlers } from './handlers';
export const server = setupServer(...handlers);
// src/test/setup.js
import '@testing-library/jest-dom/vitest';
import { cleanup } from '@testing-library/react';
import { server } from '../mocks/server';
beforeAll(() => server.listen({ onUnhandledRequest: 'error' }));
afterEach(() => {
server.resetHandlers(); // har testdan keyin — test ichidagi o'zgarishlarni bekor qilish
cleanup();
});
afterAll(() => server.close());
onUnhandledRequest: 'error' — handler yozilmagan so'rov yuborilsa, test yiqiladi. Shunda hech qanday so'rov tasodifan haqiqiy serverga ketmaydi va unutilgan handler lar darhol ko'rinadi.
Testlarda
Endi testlarda hech qanday mock yo'q:
it('foydalanuvchilar ro\'yxatini ko\'rsatadi', async () => {
renderWithProviders(<Foydalanuvchilar />);
expect(await screen.findByText('Ali Valiyev')).toBeInTheDocument();
expect(screen.getByText('Zarina Karimova')).toBeInTheDocument();
});
Komponent → TanStack Query → API moduli → fetch → MSW → javob. Butun zanjir haqiqiy, faqat server soxta.
Bitta test uchun boshqa javob — server.use
import { http, HttpResponse } from 'msw';
import { server } from '../mocks/server';
it('server xatosida xabar ko\'rsatadi', async () => {
server.use(
http.get('/api/users', () => {
return HttpResponse.json({ xabar: 'Ichki xato' }, { status: 500 });
})
);
renderWithProviders(<Foydalanuvchilar />);
expect(await screen.findByRole('alert')).toHaveTextContent(/xato/i);
});
it("bo'sh ro'yxatda xabar ko'rsatadi", async () => {
server.use(http.get('/api/users', () => HttpResponse.json([])));
renderWithProviders(<Foydalanuvchilar />);
expect(await screen.findByText("Hali foydalanuvchilar yo'q")).toBeInTheDocument();
});
server.use — handler ni vaqtincha almashtiradi; afterEach dagi resetHandlers() uni asl holatiga qaytaradi.
Maxsus holatlar
import { http, HttpResponse, delay } from 'msw';
// Tarmoq uzilishi
http.get('/api/users', () => HttpResponse.error());
// Sekin javob — skeleton ni tekshirish uchun
http.get('/api/users', async () => {
await delay(2000);
return HttpResponse.json([]);
});
// Abadiy kutish
http.get('/api/users', async () => {
await delay('infinite');
});
So'rov to'g'ri yuborilganini tekshirish
it('forma ma\'lumotlarini to\'g\'ri yuboradi', async () => {
let yuborilgan;
server.use(
http.post('/api/users', async ({ request }) => {
yuborilgan = await request.json();
return HttpResponse.json({ id: 3, ...yuborilgan }, { status: 201 });
})
);
const user = userEvent.setup();
renderWithProviders(<YangiFoydalanuvchi />);
await user.type(screen.getByLabelText('Ism'), 'Sardor');
await user.type(screen.getByLabelText('Email'), 'sardor@mail.uz');
await user.click(screen.getByRole('button', { name: 'Saqlash' }));
expect(await screen.findByText("Foydalanuvchi qo'shildi")).toBeInTheDocument();
expect(yuborilgan).toEqual({ ism: 'Sardor', email: 'sardor@mail.uz' });
});
Brauzerda — backend siz ishlab chiqish
MSW ning eng kuchli tomoni: o'sha handler lar brauzerda ham ishlaydi. Backend tayyor bo'lmasa ham frontend to'liq ishlaydi:
npx msw init public/ --save
Bu buyruq public/mockServiceWorker.js faylini yaratadi — Service Worker so'rovlarni ushlaydi.
// src/mocks/browser.js
import { setupWorker } from 'msw/browser';
import { handlers } from './handlers';
export const worker = setupWorker(...handlers);
// src/main.jsx
async function mocklarniYoq() {
if (import.meta.env.DEV && import.meta.env.VITE_MOCK === 'true') {
const { worker } = await import('./mocks/browser');
await worker.start({ onUnhandledRequest: 'bypass' });
}
}
mocklarniYoq().then(() => {
createRoot(document.getElementById('root')).render(<App />);
});
VITE_MOCK=true npm run dev
- Faqat development da va kerak bo'lganda yoqiladi; dynamic import (69-dars) — MSW production bundle ga kirmaydi
- Network tabida so'rovlar ko'rinadi — xuddi haqiqiy kabi
onUnhandledRequest: 'bypass'— handler yo'q so'rovlar (rasmlar, shriftlar) haqiqiy tarmoqqa o'tadi
Bitta handler to'plami — testlar, development, Storybook va demo uchun.
[!TIP] Handler lardagi soxta ma'lumot haqiqiy API ga mos bo'lishi kerak — aks holda testlar o'tadi, production esa buziladi. Backend OpenAPI (Swagger) hujjati bersa, undan handler larni avtomatik yaratish vositalari bor. Kamida maydon nomlari va turlarini backend jamoasi bilan kelishib oling.
Nima uchun shunday?
Nima uchun so'rovlarni tarmoq darajasida ushlash fetch yoki modulni mock qilishdan yaxshiroq?
Chunki mock qanchalik pastda bo'lsa, test shunchalik ko'p haqiqiy kodni tekshiradi. Modulni mock qilsangiz — URL to'g'ri tuzilganmi, header lar qo'shilganmi, javob to'g'ri qayta ishlanganmi, 404 da xato tashlanadimi — hech biri tekshirilmaydi; aynan shu joylarda esa xatolar ko'p bo'ladi. MSW ilova va tashqi dunyo o'rtasidagi eng oxirgi chegarada turadi: undan oldingi hamma narsa — haqiqiy. Bundan tashqari, testlar implementatsiyaga bog'lanmaydi: fetch dan Axios ga, TanStack Query dan RTK Query ga o'tsangiz ham, testlar o'zgarmasdan o'tadi — chunki ular "qanday so'rov yuborildi" ni emas, "foydalanuvchi nima ko'rdi" ni tekshiradi.
Xulosa
Eslab qoling:
- MSW so'rovlarni tarmoq darajasida ushlaydi; kod (fetch/Axios, API modul, hook) — haqiqiy
- Handler lar:
http.get('/api/users/:id', ({ params, request }) => HttpResponse.json(...)) - Node (testlar):
setupServer(...handlers);listen({ onUnhandledRequest: 'error' }),resetHandlers(),close() - Bitta test uchun —
server.use(...): xato (500), bo'sh ro'yxat,HttpResponse.error(),delay - Yuborilgan ma'lumotni handler ichida
request.json()bilan tekshirish - Brauzer:
npx msw init public/,setupWorker, faqat DEV da dynamic import bilan - Bitta handler to'plami — testlar, development, demo; ma'lumot haqiqiy API ga mos bo'lsin
Keyingi bo'lim
12-bo'lim: Ilg'or mavzular — Error Boundary, Portals, Suspense, Concurrent rendering va Server Components.