| name | vitest |
| description | Vitest unit testing patterns with React Testing Library. Trigger: When writing unit tests for React components, hooks, or utilities.
|
| license | Apache-2.0 |
| metadata | {"author":"prowler-cloud","version":"1.0","scope":["root","ui"],"auto_invoke":["Writing Vitest tests","Writing React component tests","Writing unit tests for UI","Testing hooks or utilities"]} |
| allowed-tools | Read, Edit, Write, Glob, Grep, Bash, Task |
For E2E tests: Use prowler-test-ui skill (Playwright).
This skill covers unit/integration tests with Vitest + React Testing Library.
Test Structure (REQUIRED)
Use Given/When/Then (AAA) pattern with comments:
it("should update user name when form is submitted", async () => {
const user = userEvent.setup();
const onSubmit = vi.fn();
render(<UserForm onSubmit={onSubmit} />);
await user.type(screen.getByLabelText(/name/i), "John");
await user.click(screen.getByRole("button", { name: /submit/i }));
expect(onSubmit).toHaveBeenCalledWith({ name: "John" });
});
Describe Block Organization
describe("ComponentName", () => {
describe("when [condition]", () => {
it("should [expected behavior]", () => {});
});
});
Group by behavior, NOT by method.
Query Priority (REQUIRED)
| Priority | Query | Use Case |
|---|
| 1 | getByRole | Buttons, inputs, headings |
| 2 | getByLabelText | Form fields |
| 3 | getByPlaceholderText | Inputs without label |
| 4 | getByText | Static text |
| 5 | getByTestId | Last resort only |
screen.getByRole("button", { name: /submit/i });
screen.getByLabelText(/email/i);
container.querySelector(".btn-primary");
userEvent over fireEvent (REQUIRED)
const user = userEvent.setup();
await user.click(button);
await user.type(input, "hello");
fireEvent.click(button);
Async Testing Patterns
const element = await screen.findByText(/loaded/i);
await waitFor(() => {
expect(screen.getByText(/success/i)).toBeInTheDocument();
});
await waitFor(() => expect(mockFn).toHaveBeenCalled());
await waitFor(() => expect(screen.getByText(/done/i)).toBeVisible());
await waitFor(() => {
expect(mockFn).toHaveBeenCalled();
expect(screen.getByText(/done/i)).toBeVisible();
});
Mocking
const handleClick = vi.fn();
const fetchUser = vi.fn().mockResolvedValue({ name: "John" });
afterEach(() => {
vi.restoreAllMocks();
});
vi.spyOn vs vi.mock
| Method | When to Use |
|---|
vi.spyOn | Observe without replacing (PREFERRED) |
vi.mock | Replace entire module (use sparingly) |
Common Matchers
expect(element).toBeInTheDocument();
expect(element).toBeVisible();
expect(button).toBeDisabled();
expect(input).toHaveValue("text");
expect(checkbox).toBeChecked();
expect(element).toHaveTextContent(/hello/i);
expect(element).toHaveAttribute("href", "/home");
expect(fn).toHaveBeenCalledWith(arg1, arg2);
expect(fn).toHaveBeenCalledTimes(2);
What NOT to Test
expect(component.state.isLoading).toBe(true);
expect(axios.get).toHaveBeenCalled();
expect(screen.getByText("Welcome")).toBeInTheDocument();
expect(screen.getByRole("button")).toBeDisabled();
File Organization
components/
āāā Button/
ā āāā Button.tsx
ā āāā Button.test.tsx # Co-located
ā āāā index.ts
Commands
pnpm test
pnpm test:run
pnpm test:coverage
pnpm test Button