You are viewing a free preview of this lesson.
Subscribe to unlock all 10 lessons in this course and every other course on LearningBro.
A professional React and Next.js application needs thorough testing and a reliable deployment pipeline. This lesson covers Jest, React Testing Library, testing components, hooks, and pages, end-to-end testing with Playwright, deploying to Vercel and self-hosted environments, and managing environment variables.
| Tool | Purpose |
|---|---|
| Jest | Test runner and assertion library |
| React Testing Library | Testing React components from the user's perspective |
| Playwright | End-to-end browser testing |
| MSW (Mock Service Worker) | Mocking API requests |
npm install -D jest @jest/globals ts-jest @testing-library/react @testing-library/jest-dom @testing-library/user-event jest-environment-jsdom
// jest.config.ts
import type { Config } from "jest";
import nextJest from "next/jest";
const createJestConfig = nextJest({ dir: "./" });
const config: Config = {
testEnvironment: "jsdom",
setupFilesAfterSetup: ["<rootDir>/jest.setup.ts"],
moduleNameMapper: {
"^@/(.*)$": "<rootDir>/src/$1",
},
};
export default createJestConfig(config);
// jest.setup.ts
import "@testing-library/jest-dom";
React Testing Library encourages testing from the user's perspective — find elements by their role, label, or text, not by CSS class or test ID:
// Button.tsx
interface ButtonProps {
onClick: () => void;
children: React.ReactNode;
disabled?: boolean;
}
export function Button({ onClick, children, disabled }: ButtonProps) {
return (
<button onClick={onClick} disabled={disabled}>
{children}
</button>
);
}
// Button.test.tsx
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { Button } from "./Button";
describe("Button", () => {
it("renders the button text", () => {
render(<Button onClick={() => {}}>Click Me</Button>);
expect(screen.getByRole("button", { name: /click me/i })).toBeInTheDocument();
});
it("calls onClick when clicked", async () => {
const handleClick = jest.fn();
render(<Button onClick={handleClick}>Click Me</Button>);
await userEvent.click(screen.getByRole("button", { name: /click me/i }));
expect(handleClick).toHaveBeenCalledTimes(1);
});
it("is disabled when disabled prop is true", () => {
render(<Button onClick={() => {}} disabled>Click Me</Button>);
expect(screen.getByRole("button")).toBeDisabled();
});
});
| Query | Use Case |
|---|---|
getByRole | Buttons, links, headings, inputs |
getByLabelText | Form inputs with labels |
getByText | Static text content |
getByPlaceholderText | Inputs with placeholder text |
queryByRole | Assert element is NOT present |
findByRole | Wait for element to appear (async) |
// LoginForm.test.tsx
import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import LoginForm from "./LoginForm";
describe("LoginForm", () => {
it("submits the form with email and password", async () => {
const handleSubmit = jest.fn();
render(<LoginForm onSubmit={handleSubmit} />);
await userEvent.type(
screen.getByLabelText(/email/i),
"alice@example.com"
);
await userEvent.type(screen.getByLabelText(/password/i), "secret123");
await userEvent.click(screen.getByRole("button", { name: /log in/i }));
await waitFor(() => {
expect(handleSubmit).toHaveBeenCalledWith({
email: "alice@example.com",
password: "secret123",
});
});
});
it("shows validation error for empty email", async () => {
render(<LoginForm onSubmit={() => {}} />);
await userEvent.click(screen.getByRole("button", { name: /log in/i }));
expect(await screen.findByText(/email is required/i)).toBeInTheDocument();
});
});
// useCounter.ts
import { useState, useCallback } from "react";
export function useCounter(initial = 0) {
const [count, setCount] = useState(initial);
const increment = useCallback(() => setCount((c) => c + 1), []);
const decrement = useCallback(() => setCount((c) => c - 1), []);
const reset = useCallback(() => setCount(initial), [initial]);
return { count, increment, decrement, reset };
}
// useCounter.test.ts
import { renderHook, act } from "@testing-library/react";
import { useCounter } from "./useCounter";
describe("useCounter", () => {
it("starts at the initial value", () => {
const { result } = renderHook(() => useCounter(5));
expect(result.current.count).toBe(5);
});
it("increments", () => {
const { result } = renderHook(() => useCounter(0));
act(() => result.current.increment());
expect(result.current.count).toBe(1);
});
Subscribe to continue reading
Get full access to this lesson and all 10 lessons in this course.