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.