Full Stack Development

Component Testing



by Tomas Trescak t.trescak@westernsydney.edu.au

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