React Testing Skill
Testing Tools
- Use
Vitest as the primary test runner.
- Use
@testing-library/react for component behavior testing.
- Use
Playwright for all End-to-End (E2E) testing.
Directory Structure
All test files are located under web/tests/ and are organized as follows:
tests/unit/: Contains unit tests testing components, hooks, features, and utils in isolation using Vitest and React Testing Library.
tests/e2e/: Contains end-to-end integration tests that run in a browser using Playwright.
tests/mocks/: Contains mocks for external integrations or APIs.
tests/fixtures/: Contains reusable test fixtures or data.
tests/test_utils.tsx: Contains common testing utilities, such as a custom render wrapper.
Standards & Best Practices
- Test user interactions rather than implementation details.
- Ensure all tests are isolated and don't depend on global state.
- No Comments Needed: No need to add comments inside any of the test code (like
// Arrange, // Act, // Assert). The test or it description strings are enough to explain the test logic.
- Code Coverage: Ensure test coverage is strictly more than 80% of the lines.
- Mocking: Use
vi.fn() for mock functions and vi.mock() for module mocks in Vitest.
Example: Unit Test (Vitest + Testing Library)
import { fireEvent, render, screen } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest";
import { ArchiveCard } from "@/components/ArchiveCard";
const todo = {
id: "todo-1",
title: "Old Project Notes",
isCompleted: false,
createdAt: "2024-01-01T00:00:00Z",
updatedAt: "2024-03-01T00:00:00Z",
};
describe("ArchiveCard", () => {
it("renders the todo title", () => {
render(<ArchiveCard todo={todo} />);
expect(screen.getByText("Old Project Notes")).toBeInTheDocument();
});
it("calls onRestore with the todo id when Restore is clicked", () => {
const
render(
<ArchiveCard todo={todo} />,
);
fireEvent.click(screen.getByRole("button"));
fireEvent.click(screen.getByText("Restore"));
expect(onRestore).toHaveBeenCalledWith("todo-1");
});
});
Example: E2E Test (Playwright)
import { expect, test } from "@playwright/test";
const API = process.env.API_URL || "http://localhost:8000";
const TEST_PASSWORD = "securepassword123";
test.describe("Sign Up Flow", () => {
test.afterAll(async ({ request }, testInfo) => {
const email = `e2e_signup_w${testInfo.workerIndex}@example.com`;
const res = await request.post(`${API}/v1/auth/signin`, {
data: { email, password: TEST_PASSWORD },
});
if (res.ok()) {
await request.delete(`${API}/v2/user/me`);
}
});
test("User can sign up successfully and is redirected to sign in", async ({
page,
}, testInfo) => {
const email = `e2e_signup_w${testInfo.workerIndex}@example.com`;
await page.goto("/");
await page
.getByRole("button", { name: "Sign up", exact: true })
.first()
.click();
await page.getByPlaceholder("you@example.com").fill(email);
await page.getByPlaceholder("••••••••").fill(TEST_PASSWORD);
await page.locator('button[type="submit"]').click();
await expect(page.getByText("Account created!")).toBeVisible();
await expect(page.locator('button[type="submit"]')).toHaveText(/Sign in/i);
});
});
1---2name: react-testing3description: Frontend testing standards using Vitest, React Testing Library, and Playwright. Use when writing UI tests.4---5# React Testing Skill67## Testing Tools89- Use `Vitest` as the primary test runner.10- Use `@testing-library/react` for component behavior testing.11- Use `Playwright` for all End-to-End (E2E) testing.1213## Directory Structure1415All test files are located under `web/tests/` and are organized as follows:1617* `tests/unit/`: Contains unit tests testing components, hooks, features, and utils in isolation using Vitest and React Testing Library.18* `tests/e2e/`: Contains end-to-end integration tests that run in a browser using Playwright.19* `tests/mocks/`: Contains mocks for external integrations or APIs.20* `tests/fixtures/`: Contains reusable test fixtures or data.21* `tests/test_utils.tsx`: Contains common testing utilities, such as a custom `render` wrapper.2223## Standards & Best Practices2425- Test user interactions rather than implementation details.26- Ensure all tests are isolated and don't depend on global state.27- **No Comments Needed**: No need to add comments inside any of the test code (like `// Arrange`, `// Act`, `// Assert`). The `test` or `it` description strings are enough to explain the test logic.28- **Code Coverage**: **Ensure test coverage is strictly more than 80% of the lines.**29- **Mocking**: Use `vi.fn()` for mock functions and `vi.mock()` for module mocks in Vitest.3031## Example: Unit Test (Vitest + Testing Library)3233```tsx34import { fireEvent, render, screen } from "@testing-library/react";35import { describe, expect, it, vi } from "vitest";36import { ArchiveCard } from "@/components/ArchiveCard";3738const todo = {39 id: "todo-1",40 title: "Old Project Notes",41 isCompleted: false,42 createdAt: "2024-01-01T00:00:00Z",43 updatedAt: "2024-03-01T00:00:00Z",44};4546describe("ArchiveCard", () => {47 it("renders the todo title", () => {48 render(<ArchiveCard todo={todo} onRestore={vi.fn()} onDelete={vi.fn()} />);49 expect(screen.getByText("Old Project Notes")).toBeInTheDocument();50 });5152 it("calls onRestore with the todo id when Restore is clicked", () => {53 const onRestore = vi.fn();54 render(55 <ArchiveCard todo={todo} onRestore={onRestore} onDelete={vi.fn()} />,56 );57 fireEvent.click(screen.getByRole("button"));58 fireEvent.click(screen.getByText("Restore"));59 expect(onRestore).toHaveBeenCalledWith("todo-1");60 });61});62```6364## Example: E2E Test (Playwright)6566```typescript67import { expect, test } from "@playwright/test";6869const API = process.env.API_URL || "http://localhost:8000";70const TEST_PASSWORD = "securepassword123";7172test.describe("Sign Up Flow", () => {73 test.afterAll(async ({ request }, testInfo) => {74 const email = `e2e_signup_w${testInfo.workerIndex}@example.com`;75 const res = await request.post(`${API}/v1/auth/signin`, {76 data: { email, password: TEST_PASSWORD },77 });78 if (res.ok()) {79 await request.delete(`${API}/v2/user/me`);80 }81 });8283 test("User can sign up successfully and is redirected to sign in", async ({84 page,85 }, testInfo) => {86 const email = `e2e_signup_w${testInfo.workerIndex}@example.com`;87 await page.goto("/");88 await page89 .getByRole("button", { name: "Sign up", exact: true })90 .first()91 .click();92 await page.getByPlaceholder("you@example.com").fill(email);93 await page.getByPlaceholder("••••••••").fill(TEST_PASSWORD);94 await page.locator('button[type="submit"]').click();95 await expect(page.getByText("Account created!")).toBeVisible();96 await expect(page.locator('button[type="submit"]')).toHaveText(/Sign in/i);97 });98});99```