Testing — Vitest, Jest & Playwright
Test ছাড়া কোড মানে — ভবিষ্যতের bug
1. Why Test
- Catch regressions when you refactor
- Document expected behaviour as runnable specs
- Force you to write smaller, more decoupled functions
- Catch the "I forgot the edge case" failures before users do
2. The Test Pyramid
| Layer | Tool | Speed | Coverage |
|---|---|---|---|
| Unit (single function) | Vitest, Jest | Milliseconds | ~70% of tests |
| Integration (module + module) | Vitest | Tens of ms | ~20% |
| E2E (real browser) | Playwright, Cypress | Seconds | ~10% |
3. Vitest in 30 Seconds
$ npm install -D vitest
// math.js
export const add = (a, b) => a + b;
// math.test.js
import { describe, it, expect } from "vitest";
import { add } from "./math.js";
describe("add", () => {
it("sums positive numbers", () => expect(add(2, 3)).toBe(5));
it("works with negatives", () => expect(add(-1, 1)).toBe(0));
it("throws nothing weird", () => expect(add(0.1, 0.2)).toBeCloseTo(0.3));
});
// package.json
"scripts": { "test": "vitest" }
$ npm test # watch mode by default
4. Common Matchers
expect(x).toBe(y); // ===
expect(arr).toEqual(other); // deep equal
expect(arr).toContain(item);
expect(fn).toThrow();
expect(fn).toThrow(/network/);
expect(s).toMatch(/regex/);
expect(p).resolves.toBe(42); // for promises
expect(p).rejects.toThrow();
5. Async Tests
it("fetches data", async () => {
const data = await getUser(1);
expect(data.id).toBe(1);
});
6. Sandbox-Safe "Mini Test Runner"
To get a feel for what the matchers do, here's a 12-line runner you can run in this page.
function expect(actual) {
return {
toBe(exp) {
if (actual !== exp) throw new Error(`${actual} !== ${exp}`);
},
toEqual(exp) {
if (JSON.stringify(actual) !== JSON.stringify(exp))
throw new Error("deep mismatch");
},
toThrow() {
try { actual(); }
catch { return; }
throw new Error("didn't throw");
}
};
}
function test(name, fn) {
try { fn(); console.log("✓", name); }
catch (e) { console.log("✗", name, "-", e.message); }
}
const add = (a, b) => a + b;
test("add 2+3", () => expect(add(2, 3)).toBe(5));
test("add -1+1", () => expect(add(-1, 1)).toBe(0));
test("intentional fail", () => expect(add(2, 2)).toBe(5));
test("deep equal", () =>
expect([1, 2]).toEqual([1, 2]));
7. Mocking
import { vi } from "vitest";
it("calls onSave with right value", () => {
const onSave = vi.fn();
submitForm({ name: "Arif" }, onSave);
expect(onSave).toHaveBeenCalledWith({ name: "Arif" });
});
// Mock a module
vi.mock("./db.js", () => ({ load: () => ({ id: 1 }) }));
8. End-to-End with Playwright
$ npm init playwright@latest
import { test, expect } from "@playwright/test";
test("user can sign up", async ({ page }) => {
await page.goto("http://localhost:5173");
await page.getByLabel("Email").fill("a@x.com");
await page.getByLabel("Password").fill("hunter2!");
await page.getByRole("button", { name: "Sign up" }).click();
await expect(page.getByText("Welcome")).toBeVisible();
});
9. Glossary (শব্দকোষ)
| Term | Meaning | বাংলায় |
|---|---|---|
| Unit test | Tests one function in isolation; very fast, deterministic. | এক function-এর isolated test; দ্রুত ও deterministic। |
| Integration test | Tests two or more modules together. | একাধিক module একসাথে test। |
| E2E test | Drives the real browser through the app like a user. | Real browser-এ user-এর মতো test। |
| Vitest | Modern, fast, Vite-native unit test runner. | Vite-based আধুনিক fast unit test runner। |
| Jest | Popular older unit test runner. | জনপ্রিয় পুরোনো test runner। |
| Playwright | Cross-browser E2E test framework. | Cross-browser E2E framework। |
| Matcher | toBe, toEqual, toThrow — assertion verbs. | Assertion-এর verb — toBe ইত্যাদি। |
| Mock | Fake replacement for a dependency; lets you isolate logic. | Dependency-র fake replacement। |
| Coverage | % of lines/branches exercised by tests. | Test দ্বারা ছোঁয়া code-এর শতাংশ। |
| Test pyramid | Many unit, some integration, few E2E. | অনেক unit, কিছু integration, অল্প E2E। |
10. Practice Problems
- Write 3 tests for a divide(a, b) function (positive, negative, b=0).
✨ Show Answer
a1.jsconst divide = (a, b) => { if (b === 0) throw new Error("div0"); return a / b; }; function test(name, fn) { try { fn(); console.log("✓", name); } catch (e) { console.log("✗", name, e.message); } } test("10/2", () => { if (divide(10, 2) !== 5) throw new Error("x"); }); test("-6/3", () => { if (divide(-6, 3) !== -2) throw new Error("x"); }); test("div0 throws", () => { try { divide(1, 0); throw new Error("didn't throw"); } catch (e) { if (e.message !== "div0") throw e; } }); - Test debounce with a fake timer (logic).
✨ Show Answer
// Vitest fake timers import { vi } from "vitest"; it("debounce fires only after silence", () => { vi.useFakeTimers(); const fn = vi.fn(); const d = debounce(fn, 100); d(); d(); d(); vi.advanceTimersByTime(100); expect(fn).toHaveBeenCalledTimes(1); }); - Why does aiming for 100% test coverage often backfire?
✨ Show Answer
Answer: The last 5–10% comes from trivial getters and one-line passthroughs that catch nothing. You spend hours mocking the world to lift the number — and the resulting tests are brittle, slow, and produce false confidence. Aim for 70–85% with sharp tests on the logic that actually matters.
- Build a runnable assertion that an array is sorted.
✨ Show Answer
a4.jsconst isSorted = a => a.every((v, i) => i === 0 || a[i - 1] <= v); console.log(isSorted([1, 2, 2, 3])); console.log(isSorted([2, 1])); - Compare unit test vs E2E in two sentences.
✨ Show Answer
Answer: Unit tests check a single function in isolation — fast, deterministic, useful for refactor confidence. E2E tests drive a real browser through the actual UI — slow, more flaky, but they catch the integration bugs that unit tests cannot.
- Sketch a Playwright test for a login flow.
✨ Show Answer
test("login", async ({ page }) => { await page.goto("/login"); await page.getByLabel("Email").fill("a@x.com"); await page.getByLabel("Password").fill("pw"); await page.getByRole("button", { name: "Sign in" }).click(); await expect(page.getByText("Dashboard")).toBeVisible(); }); - Why is mocking the network usually better than hitting it in tests?
✨ Show Answer
Answer: Real network is slow, flaky, and depends on services you don't control. A mock gives instant deterministic responses — the test runs in milliseconds and never fails because someone else's API is down. Reserve real-network tests for one or two end-to-end "smoke" runs.
- In one paragraph, when should you write a test before the code?
✨ Show Answer
Answer: Test-first works best for pure functions with clear inputs/outputs (parsers, validators, math), where writing the test forces you to nail the contract. For UI work or research code, writing tests after a working sketch is usually faster — premature tests against unstable APIs slow you down. Pragmatism beats dogma.
Summary — Module 37
Test pyramid: many unit tests, some integration, a few E2E. Vitest is the modern fast unit runner; Playwright owns end-to-end. Mock the network in unit tests; hit it in E2E. Aim for high-leverage coverage, not 100%.