Testing
/
Introduction
- Why test components?
- Integration matters too!
- React 19, Vitest, react-testing-library
- Simple Installation
// vitest.config.ts
import { defineConfig } from "vitest/config";
import react from "@vitejs/plugin-react";
export default defineConfig({
plugins: [react()],
test: {
environment: "jsdom",
globals: true,
setupFiles: "./tests/setup.ts",
},
});
// tests/setup.ts
import "@testing-library/jest-dom";
// package.json
{
"scripts": {
"test": "vitest run",
"test:watch": "vitest"
}
}
Testing
/
Example
// components/PostPreview.tsx
import { formatDate } from "../utils/date";
interface PostPreviewProps { title: string; date: Date; }
export function PostPreview({ title, date }: PostPreviewProps) {
return (
<div>
<h2>{title}</h2>
<p>{formatDate(date)}</p>
</div>
);
}
// tests/PostPreview.test.tsx
import { render, screen } from "@testing-library/react";
import { PostPreview } from "../components/PostPreview";
describe("PostPreview", () => {
it("renders title and date", () => {
render(<PostPreview title="My Post" date={new Date("2025-04-07")} />);
expect(screen.getByText("My Post")).toBeInTheDocument();
expect(screen.getByText("April 7, 2025")).toBeInTheDocument();
});
});
Testing
/
Server Components
// components/PostContent.tsx
interface PostContentProps { postId: string; }
async function fetchPostBody(postId: string): Promise<string> {
const res = await fetch(`/api/post/${postId}`);
return res.text();
}
export async function PostContent({ postId }: PostContentProps) {
const body = await fetchPostBody(postId);
return <p>{body}</p>;
}
// tests/PostContent.test.tsx
import { render, screen, waitFor } from "@testing-library/react";
import { PostContent } from "../components/PostContent";
import { Suspense } from "react";
describe("PostContent", () => {
it("renders async post body", async () => {
global.fetch = vi.fn().mockResolvedValue({ text: () => Promise.resolve("Hello!") } as any);
render(
<Suspense fallback={<p>Loading...</p>}>
<PostContent postId="1" />
</Suspense>
);
expect(screen.getByText("Loading...")).toBeInTheDocument();
await waitFor(() => expect(screen.getByText("Hello!")).toBeInTheDocument());
});
});
Testing
/
Spying and Mocking
// tests/PostList.test.tsx
import { render, screen, fireEvent } from "@testing-library/react";
import { PostList } from "../components/PostList";
describe("PostList", () => {
it("filters posts by tag", async () => {
const consoleSpy = vi.spyOn(console, 'log');
const fetchSpy = vi.spyOn(global, "fetch")
.mockResolvedValueOnce({
json: () => Promise.resolve([{ title: "Post 1", date: "2025-04-07", tag: "life" }]),
} as any)
.mockResolvedValueOnce({
json: () => Promise.resolve([{ title: "Tech Post", date: "2025-04-08", tag: "tech" }]),
} as any);
render(<PostList />);
expect(consoleSpy).toHaveBeenCalledWith("Hello World!")
expect(await screen.findByText("Post 1")).toBeInTheDocument();
fireEvent.click(screen.getByText("Filter Tech"));
expect(await screen.findByText("Tech Post")).toBeInTheDocument();
expect(fetchSpy).toHaveBeenCalledWith("/api/posts?tag=tech");
});
});
Testing
/
Wrap Up
- Simple Setup
- Component tests for UI
- User Interactions
- Integration for flows
- Coverage
- Spy & mock smartly