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.