Testing TypeScript Code: Building with Confidence

Testing TypeScript Code: Building with Confidence

Testing is not an optional "extra" in professional development; it is a core part of the engineering process. TypeScript and automated testing work together to create a "double safety net" for your application.


1. The Testing Stack: Jest and ts-jest

Jest is the most popular testing framework for JavaScript. To use it with TypeScript, we use a transformer called ts-jest.

Installation

npm install --save-dev jest ts-jest @types/jest
npx ts-jest config:init

2. Writing Unit Tests

Unit tests focus on individual functions. Because TypeScript ensures your types are correct, your tests can focus on the logic.

// math.ts
export const add = (a: number, b: number) => a + b;

// math.test.ts
import { add } from "./math";

describe("Math Utils", () => {
    it("should correctly add two numbers", () => {
        expect(add(2, 3)).toBe(5);
    });

    it("should handle negative numbers", () => {
        expect(add(-1, 1)).toBe(0);
    });
});

3. Mocking Dependencies

Mocking allows you to isolate the code you are testing by replacing complex dependencies (like a database or an API) with simple, fake versions.

// api.ts
export const fetchData = async () => { /* calls external API */ };

// service.ts
import { fetchData } from "./api";
export const processData = async () => {
    const data = await fetchData();
    return data.length;
};

// service.test.ts
import { processData } from "./service";
import * as api from "./api";

jest.mock("./api"); // Replace the real API module

it("should process mocked data", async () => {
    (api.fetchData as jest.Mock).mockResolvedValue(["item1", "item2"]);
    const result = await processData();
    expect(result).toBe(2);
});

4. Testing React Components

Using React Testing Library (RTL), we can test our typed components as if we were a real user.

import { render, screen, fireEvent } from "@testing-library/react";
import { MyButton } from "./MyButton";

it("calls onClick when clicked", () => {
    const handleClick = jest.fn(); // Mock function
    render(<MyButton label="Click Me" onClick={handleClick} />);
    
    const button = screen.getByText("Click Me");
    fireEvent.click(button);
    
    expect(handleClick).toHaveBeenCalledTimes(1);
});

5. Type-Safe Testing Patterns

  • jest.fn<ReturnType, Parameters>(): You can type your mock functions to ensure the test itself doesn't have logic errors.
  • Factory Functions: Use helper functions to create mock data objects that match your interfaces.
const createMockUser = (overrides?: Partial<User>): User => ({
    id: 1,
    name: "Default User",
    ...overrides
});

6. Summary

  • Unit Tests verify the logic of your functions.
  • Mocking isolates your code from external side effects.
  • React Testing Library ensures your UI behaves correctly.
  • TypeScript prevents your tests from becoming outdated when interfaces change.