تماس با ما

تست: مفاهیم، Jest و Vitest

تست: مفاهیم، Jest و Vitest؛ آموزش مسیر فرانت‌اند به زبان ساده، همراه با مثال و خروجی.

چرا تست؟

اطمینان از درستی، جلوگیری از رگرسیون، امکان refactor بی‌ترس، مستند بودن رفتار. و برای AI agentها «سقف کیفیت» حلقه‌ی بازخورد است (TDD و AI).

هرم تست

        /\          E2E      (کم، کند، واقعی)        ← Playwright / Cypress
       /  \
      /----\        Integration (متوسط)             ← چند واحد با هم
     /      \
    /--------\      Unit      (زیاد، سریع)          ← Jest / Vitest

Testing Trophy (Kent C. Dodds): بیشتر روی تست یکپارچگی و «شبیه کاربر».

انواع دیگر: تست بصری (screenshot)، تست دسترس‌پذیری (axe)، تست عملکرد، smoke test، contract test.

ساختار تست: AAA

Arrange (آماده‌سازی) ← Act (اجرا) ← Assert (بررسی)

Vitest (پیشنهادی با Vite)

npm i -D vitest
npx vitest            # حالت watch
npx vitest run        # یک‌بار (CI)
npx vitest --coverage
// sum.ts
export const sum = (a: number, b: number) => a + b;

// sum.test.ts
import { describe, it, expect } from "vitest";
import { sum } from "./sum";

describe("sum", () => {
  it("جمع دو عدد", () => {
    expect(sum(1, 2)).toBe(3);
  });
  it.each([[1, 1, 2], [2, 3, 5]])("sum(%i, %i) = %i", (a, b, r) => {
    expect(sum(a, b)).toBe(r);
  });
});

Jest تقریباً همان API را دارد (describe/it/expect)؛ Jest با npx jest.

Matcherها

expect(x).toBe(3);                    // ===
expect(obj).toEqual({ a: 1 });        // مقایسه‌ی ساختاری
expect(arr).toContain("a");
expect(arr).toHaveLength(2);
expect(str).toMatch(/hello/);
expect(n).toBeGreaterThan(2);
expect(n).toBeCloseTo(0.3);           // اعداد اعشاری
expect(v).toBeNull(); expect(v).toBeDefined(); expect(v).toBeTruthy();
expect(() => fn()).toThrow("خطا");
expect(promise).resolves.toBe(1);
expect(promise).rejects.toThrow();
expect(obj).toMatchObject({ a: 1 });
expect(x).not.toBe(5);
expect(value).toMatchSnapshot();      // یا toMatchInlineSnapshot

Setup / Teardown

beforeAll(() => {});   afterAll(() => {});
beforeEach(() => {});  afterEach(() => {});

Async

it("داده را می‌گیرد", async () => {
  const user = await getUser(1);
  expect(user.name).toBe("Ali");
});

Mock، Spy و Stub

import { vi } from "vitest";      // در Jest: jest.fn / jest.mock

const fn = vi.fn().mockReturnValue(42);
fn(1, 2);
expect(fn).toHaveBeenCalledWith(1, 2);
expect(fn).toHaveBeenCalledTimes(1);

vi.spyOn(console, "error").mockImplementation(() => {});

// mock یک ماژول
vi.mock("./api", () => ({ getUser: vi.fn().mockResolvedValue({ name: "Ali" }) }));

// زمان
vi.useFakeTimers();
setTimeout(cb, 1000);
vi.advanceTimersByTime(1000);
vi.useRealTimers();

مثل fetch را با MSW (Mock Service Worker) شبیه‌سازی کن تا به شبکه نخورد.

تست کامپوننت (Testing Library)

فلسفه: مثل کاربر تست کن، نه جزئیات پیاده‌سازی.

npm i -D @testing-library/react @testing-library/jest-dom @testing-library/user-event jsdom
// vite.config.ts → test: { environment: "jsdom", setupFiles: "./setup.ts" }
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { Counter } from "./Counter";

it("شمارنده زیاد می‌شود", async () => {
  render(<Counter />);
  const btn = screen.getByRole("button", { name: /۰|0/ });
  await userEvent.click(btn);
  expect(screen.getByRole("button")).toHaveTextContent("1");
});

اولویت انتخاب المان: getByRole > getByLabelText > getByText > getByTestId (آخرین راه).

  • getBy* (باید باشد)، queryBy* (ممکن است نباشد → null)، findBy* (async، منتظر می‌ماند).
  • برای Vue: Vue Test Utils؛ برای Angular: TestBed.

پوشش (Coverage)

درصد خطوط/شاخه‌ها که تست اجرا کرده. عدد ۱۰۰٪ هدف نیست؛ مسیرهای مهم را پوشش بده.

TDD

۱. 🔴 تست شکست‌خورده بنویس ۲. 🟢 حداقل کد برای قبولی ۳. 🔵 Refactor.

نکات

  • تست‌ها باید مستقل، سریع و deterministic باشند (بدون وابستگی به ترتیب/زمان/شبکه).
  • هر تست یک رفتار.
  • نام تست = توضیح رفتار.
  • تست‌های flaky را فوراً درست کن.
  • در CI اجرا کن (GitHub Actions).

تمرین

۱. تابع slugify(text) را با TDD بنویس. ۲. کامپوننت فرم لاگین را با Testing Library تست کن (خطای اعتبارسنجی + submit). ۳. با MSW پاسخ یک API را mock کن.

ادامه: E2E

ایده‌ای دارید؟ برایمان بگویید

مشاوره و پیشنهاد اولیه رایگان است.

تماس با ما