Jest, React Testing Library, and testing best practices
# Testing React Components Guide for Google Antigravity
Master React component testing using Jest and React Testing Library in your Google Antigravity projects. This comprehensive guide covers unit testing, integration testing, mocking, and test-driven development patterns.
## Testing Setup Configuration
Configure Jest with React Testing Library:
```typescript
// jest.config.ts
import type { Config } from "jest";
import nextJest from "next/jest";
const createJestConfig = nextJest({
dir: "./",
});
const config: Config = {
displayName: "unit",
testEnvironment: "jsdom",
setupFilesAfterEnv: ["<rootDir>/jest.setup.ts"],
testPathIgnorePatterns: ["/node_modules/", "/.next/", "/e2e/"],
moduleNameMapper: {
"^@/(.*)$": "<rootDir>/src/$1",
},
collectCoverageFrom: [
"src/**/*.{js,jsx,ts,tsx}",
"!src/**/*.d.ts",
"!src/**/*.stories.{js,jsx,ts,tsx}",
"!src/**/index.ts",
],
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80,
},
},
};
export default createJestConfig(config);
```
```typescript
// jest.setup.ts
import "@testing-library/jest-dom";
import { server } from "./src/mocks/server";
// Mock next/navigation
jest.mock("next/navigation", () => ({
useRouter: () => ({
push: jest.fn(),
replace: jest.fn(),
back: jest.fn(),
prefetch: jest.fn(),
}),
usePathname: () => "/",
useSearchParams: () => new URLSearchParams(),
}));
// Start MSW server
beforeAll(() => server.listen({ onUnhandledRequest: "error" }));
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
```
## Component Unit Testing
Test individual components in isolation:
```typescript
// src/components/Button/Button.test.tsx
import { render, screen, fireEvent } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { Button } from "./Button";
describe("Button", () => {
it("renders with correct text", () => {
render(<Button>Click me</Button>);
expect(screen.getByRole("button", { name: /click me/i })).toBeInTheDocument();
});
it("applies variant classes correctly", () => {
render(<Button variant="primary">Primary</Button>);
const button = screen.getByRole("button");
expect(button).toHaveClass("bg-gradient-to-r");
});
it("handles click events", async () => {
const handleClick = jest.fn();
const user = userEvent.setup();
render(<Button onClick={handleClick}>Click me</Button>);
await user.click(screen.getByRole("button"));
expect(handleClick).toHaveBeenCalledTimes(1);
});
it("shows loading state correctly", () => {
render(<Button isLoading>Loading</Button>);
const button = screen.getByRole("button");
expect(button).toBeDisabled();
expect(screen.getByText(/loading/i)).toBeInTheDocument();
});
it("prevents clicks when disabled", async () => {
const handleClick = jest.fn();
const user = userEvent.setup();
render(<Button disabled onClick={handleClick}>Disabled</Button>);
await user.click(screen.getByRole("button"));
expect(handleClick).not.toHaveBeenCalled();
});
it("renders as a link when href is provided", () => {
render(<Button href="/dashboard">Go to Dashboard</Button>);
expect(screen.getByRole("link")).toHaveAttribute("href", "/dashboard");
});
});
```
## Form Component Testing
Test complex form interactions:
```typescript
// src/components/LoginForm/LoginForm.test.tsx
import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { LoginForm } from "./LoginForm";
import { server } from "@/mocks/server";
import { http, HttpResponse } from "msw";
describe("LoginForm", () => {
const user = userEvent.setup();
it("renders all form fields", () => {
render(<LoginForm />);
expect(screen.getByLabelText(/email/i)).toBeInTheDocument();
expect(screen.getByLabelText(/password/i)).toBeInTheDocument();
expect(screen.getByRole("button", { name: /sign in/i })).toBeInTheDocument();
});
it("validates required fields on submit", async () => {
render(<LoginForm />);
await user.click(screen.getByRole("button", { name: /sign in/i }));
await waitFor(() => {
expect(screen.getByText(/email is required/i)).toBeInTheDocument();
expect(screen.getByText(/password is required/i)).toBeInTheDocument();
});
});
it("validates email format", async () => {
render(<LoginForm />);
await user.type(screen.getByLabelText(/email/i), "invalid-email");
await user.click(screen.getByRole("button", { name: /sign in/i }));
await waitFor(() => {
expect(screen.getByText(/valid email/i)).toBeInTheDocument();
});
});
it("submits form with valid data", async () => {
const onSuccess = jest.fn();
render(<LoginForm onSuccess={onSuccess} />);
await user.type(screen.getByLabelText(/email/i), "test@example.com");
await user.type(screen.getByLabelText(/password/i), "password123");
await user.click(screen.getByRole("button", { name: /sign in/i }));
await waitFor(() => {
expect(onSuccess).toHaveBeenCalledWith({
user: expect.objectContaining({ email: "test@example.com" }),
});
});
});
it("handles API errors gracefully", async () => {
server.use(
http.post("/api/auth/login", () => {
return HttpResponse.json(
{ error: "Invalid credentials" },
{ status: 401 }
);
})
);
render(<LoginForm />);
await user.type(screen.getByLabelText(/email/i), "wrong@example.com");
await user.type(screen.getByLabelText(/password/i), "wrongpassword");
await user.click(screen.getByRole("button", { name: /sign in/i }));
await waitFor(() => {
expect(screen.getByText(/invalid credentials/i)).toBeInTheDocument();
});
});
it("disables submit button while loading", async () => {
render(<LoginForm />);
await user.type(screen.getByLabelText(/email/i), "test@example.com");
await user.type(screen.getByLabelText(/password/i), "password123");
const submitButton = screen.getByRole("button", { name: /sign in/i });
await user.click(submitButton);
expect(submitButton).toBeDisabled();
});
});
```
## Custom Hook Testing
Test custom React hooks:
```typescript
// src/hooks/useDebounce.test.ts
import { renderHook, act } from "@testing-library/react";
import { useDebounce } from "./useDebounce";
describe("useDebounce", () => {
beforeEach(() => {
jest.useFakeTimers();
});
afterEach(() => {
jest.useRealTimers();
});
it("returns initial value immediately", () => {
const { result } = renderHook(() => useDebounce("initial", 500));
expect(result.current).toBe("initial");
});
it("debounces value changes", () => {
const { result, rerender } = renderHook(
({ value }) => useDebounce(value, 500),
{ initialProps: { value: "initial" } }
);
rerender({ value: "updated" });
expect(result.current).toBe("initial");
act(() => {
jest.advanceTimersByTime(500);
});
expect(result.current).toBe("updated");
});
it("cancels previous timeout on rapid changes", () => {
const { result, rerender } = renderHook(
({ value }) => useDebounce(value, 500),
{ initialProps: { value: "first" } }
);
rerender({ value: "second" });
act(() => jest.advanceTimersByTime(300) });
rerender({ value: "third" });
act(() => jest.advanceTimersByTime(300) });
expect(result.current).toBe("first");
act(() => jest.advanceTimersByTime(200) });
expect(result.current).toBe("third");
});
});
// src/hooks/useFetch.test.ts
import { renderHook, waitFor } from "@testing-library/react";
import { useFetch } from "./useFetch";
import { server } from "@/mocks/server";
import { http, HttpResponse } from "msw";
describe("useFetch", () => {
it("fetches data successfully", async () => {
const mockData = { id: 1, name: "Test" };
server.use(
http.get("/api/data", () => HttpResponse.json(mockData))
);
const { result } = renderHook(() => useFetch("/api/data"));
expect(result.current.isLoading).toBe(true);
await waitFor(() => {
expect(result.current.isLoading).toBe(false);
});
expect(result.current.data).toEqual(mockData);
expect(result.current.error).toBeNull();
});
it("handles fetch errors", async () => {
server.use(
http.get("/api/data", () => HttpResponse.json(
{ error: "Not found" },
{ status: 404 }
))
);
const { result } = renderHook(() => useFetch("/api/data"));
await waitFor(() => {
expect(result.current.error).toBeTruthy();
});
expect(result.current.data).toBeNull();
});
});
```
## Integration Testing
Test component interactions:
```typescript
// src/features/Cart/Cart.integration.test.tsx
import { render, screen, within } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { Cart } from "./Cart";
import { CartProvider } from "@/contexts/CartContext";
function renderWithProviders(ui: React.ReactElement) {
return render(
<CartProvider>
{ui}
</CartProvider>
);
}
describe("Cart Integration", () => {
const user = userEvent.setup();
it("adds items and updates total", async () => {
renderWithProviders(<Cart />);
// Add first item
await user.click(screen.getByRole("button", { name: /add product a/i }));
expect(screen.getByTestId("cart-count")).toHaveTextContent("1");
expect(screen.getByTestId("cart-total")).toHaveTextContent("$29.99");
// Add second item
await user.click(screen.getByRole("button", { name: /add product b/i }));
expect(screen.getByTestId("cart-count")).toHaveTextContent("2");
expect(screen.getByTestId("cart-total")).toHaveTextContent("$79.98");
});
it("removes items from cart", async () => {
renderWithProviders(<Cart initialItems={mockItems} />);
const cartItem = screen.getByTestId("cart-item-1");
await user.click(within(cartItem).getByRole("button", { name: /remove/i }));
expect(screen.queryByTestId("cart-item-1")).not.toBeInTheDocument();
});
});
```
Google Antigravity generates comprehensive testing strategies that ensure component reliability through unit tests, integration tests, and proper mocking patterns.This Testing prompt is ideal for developers working on:
By using this prompt, you can save hours of manual coding and ensure best practices are followed from the start. It's particularly valuable for teams looking to maintain consistency across their testing implementations.
Yes! All prompts on Antigravity AI Directory are free to use for both personal and commercial projects. No attribution required, though it's always appreciated.
This prompt works excellently with Claude, ChatGPT, Cursor, GitHub Copilot, and other modern AI coding assistants. For best results, use models with large context windows.
You can modify the prompt by adding specific requirements, constraints, or preferences. For Testing projects, consider mentioning your framework version, coding style, and any specific libraries you're using.