التجهيز · فصل ٢

تجهيز Vitest وMSW مرة واحدة

vitest.config · setupFiles · cleanup · setupServer · handlers
🎯 الهدف: تعمل config وsetup وmock server نظاف، بحيث كل test تبدأ من حالة مستقلة.

التثبيت

pnpm add -D vitest jsdom \  @testing-library/react @testing-library/dom \  @testing-library/user-event @testing-library/jest-dom msw

لو المشروع TypeScript، تأكد إن أنواع React وReact DOM موجودة.

Config

vitest.config.ts
import react from "@vitejs/plugin-react";import { defineConfig } from "vitest/config";export default defineConfig({  plugins: [react()],  test: {    environment: "jsdom",    setupFiles: ["./src/test/setup.ts"],    globals: false,  },});

globals: false يخلي imports صريحة، ويمنع كلمات زي describe تظهر بالسحر. عشان كده هنعمل cleanup بإيدنا في setup.

Handler افتراضية

src/test/academicYearsMocks.ts
import { http, HttpResponse } from "msw";export const API_URL = "http://localhost/api/academic-years";export const academicYearsHandlers = [  http.get(API_URL, () => {    return HttpResponse.json([      { id: "y1", name: "2025 / 2026" },      { id: "y2", name: "2026 / 2027" },    ]);  }),  http.post(API_URL, async ({ request }) => {    const body = (await request.json()) as { name: string };    return HttpResponse.json(      { id: "y3", name: body.name },      { status: 201 },    );  }),];

استخدمنا absolute URL لأن test شغالة في Node وfetch هناك محتاجة URL كاملة. في المشروع الحقيقي خليه config مشتركة مع التطبيق.

السيرفر والـ setup

src/test/server.ts
import { setupServer } from "msw/node";import { academicYearsHandlers } from "./academicYearsMocks";export const server = setupServer(...academicYearsHandlers);
src/test/setup.ts
import "@testing-library/jest-dom/vitest";import { cleanup } from "@testing-library/react";import { afterAll, afterEach, beforeAll, vi } from "vitest";import { server } from "./server";beforeAll(() => server.listen({ onUnhandledRequest: "error" }));afterEach(() => {  cleanup();  server.resetHandlers();  vi.restoreAllMocks();});afterAll(() => server.close());

سكربتات التشغيل

{  "scripts": {    "test": "vitest",    "test:run": "vitest run",    "test:coverage": "vitest run --coverage"  }}

vitest مناسب للـ watch محليًا، وvitest run للـ CI عشان يخلص ويخرج.

خلّصت الفصل لما

🧠 اختبر نفسك
١) ليه import jest-dom موجود في setup مش كل test؟
عشان يمدد expect مرة واحدة لكل suite ويقلل التكرار.
٢) ليه resetHandlers بعد كل test؟
عشان override لحالة error أو empty ميتسربش للاختبار اللي بعده.
٣) أنهي أمر للـ CI؟
vitest run لأنه يشغّل مرة ويخرج بدل watch mode.