Skip to main content

Module 23: Testing with TypeScript

Learn to write type-safe tests with Jest, Vitest, and other testing frameworks for TypeScript applications.


1. Jest Setup

npm install --save-dev jest @types/jest ts-jest
npx ts-jest config:init
// jest.config.js
module.exports = {
preset: "ts-jest",
testEnvironment: "node",
roots: ["<rootDir>/src"],
testMatch: ["**/__tests__/**/*.ts", "**/?(*.)+(spec|test).ts"]
};

2. Basic Test Structure

// math.ts
export function add(a: number, b: number): number {
return a + b;
}

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

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

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

3. Testing Async Functions

async function fetchUser(id: number): Promise<User> {
const response = await fetch(`/api/users/${id}`);
return response.json();
}

describe("fetchUser", () => {
it("should fetch user data", async () => {
const user = await fetchUser(1);
expect(user).toHaveProperty("name");
expect(user).toHaveProperty("email");
});
});

4. Mocking with TypeScript

// userService.ts
export class UserService {
async getUser(id: number): Promise<User> {
// Implementation
return { id, name: "Alice", email: "alice@example.com" };
}
}

// userService.test.ts
import { UserService } from "./userService";

jest.mock("./userService");

describe("UserService", () => {
it("should mock getUser", async () => {
const mockGetUser = jest.fn().mockResolvedValue({
id: 1,
name: "Mock User",
email: "mock@example.com"
});

const service = new UserService();
service.getUser = mockGetUser;

const user = await service.getUser(1);
expect(user.name).toBe("Mock User");
expect(mockGetUser).toHaveBeenCalledWith(1);
});
});

5. Type-Safe Mocks

type MockedFunction<T extends (...args: any[]) => any> = jest.MockedFunction<T>;

interface UserRepository {
findById(id: number): Promise<User | null>;
save(user: User): Promise<User>;
}

const mockUserRepo: jest.Mocked<UserRepository> = {
findById: jest.fn(),
save: jest.fn()
};

describe("UserService with mocked repository", () => {
it("should find user", async () => {
mockUserRepo.findById.mockResolvedValue({
id: 1,
name: "Alice",
email: "alice@example.com"
});

const user = await mockUserRepo.findById(1);
expect(user?.name).toBe("Alice");
});
});

6. Testing React Components

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

describe("Counter Component", () => {
it("should increment counter", () => {
render(<Counter />);
const button = screen.getByText(/increment/i);
fireEvent.click(button);
expect(screen.getByText(/count: 1/i)).toBeInTheDocument();
});
});

7. Testing with Vitest

npm install --save-dev vitest
// vitest.config.ts
import { defineConfig } from "vitest/config";

export default defineConfig({
test: {
globals: true,
environment: "node"
}
});

// math.test.ts
import { describe, it, expect } from "vitest";
import { add } from "./math";

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

8. Test Utilities

// testUtils.ts
export function createMockUser(overrides?: Partial<User>): User {
return {
id: 1,
name: "Test User",
email: "test@example.com",
...overrides
};
}

// Usage in tests
const user = createMockUser({ name: "Alice" });

9. Snapshot Testing

import { render } from "@testing-library/react";
import UserProfile from "./UserProfile";

it("should match snapshot", () => {
const { container } = render(
<UserProfile user={{ id: 1, name: "Alice", email: "alice@example.com" }} />
);
expect(container).toMatchSnapshot();
});

10. Integration Tests

import request from "supertest";
import app from "../app";

describe("User API", () => {
it("GET /api/users should return users", async () => {
const response = await request(app)
.get("/api/users")
.expect(200);

expect(Array.isArray(response.body)).toBe(true);
});

it("POST /api/users should create user", async () => {
const newUser = {
name: "Alice",
email: "alice@example.com"
};

const response = await request(app)
.post("/api/users")
.send(newUser)
.expect(201);

expect(response.body).toHaveProperty("id");
expect(response.body.name).toBe("Alice");
});
});

Key Takeaways

Jest and Vitest for TypeScript testing
Type-safe mocks with jest.Mocked
Async testing with promises
React component testing
Integration tests for APIs


Practice Exercises

Exercise 1: Test User Service

class UserService {
constructor(private repo: UserRepository) {}

async createUser(data: CreateUserInput): Promise<User> {
// Validate and create
return this.repo.save(data);
}
}

// Write comprehensive tests with mocks

Exercise 2: Test React Hook

Test a custom React hook with @testing-library/react-hooks.


Next Steps

In Module 24, we'll explore Build Tools and Bundlers including Webpack, Vite, and esbuild with TypeScript.