Mock objects and fixtures in TypeScript tests: from beginner to pro

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Introduction: Why tests without mocks are like coffee without caffeine?

\n\n

Imagine: you're writing a TypeScript application that sends email, communicates with a database, and calls an external API. You run your tests — and suddenly everything fails because the mail server is temporarily unavailable. Sound familiar?

\n\n

This is where mock objects (stubs) and fixtures (test data) come into play. They allow you to isolate your code from the outside world and make tests fast, reliable, and predictable. In this article, we'll use real examples to break down how to properly use mocks in TypeScript, what kinds of fixtures for TypeScript exist, and how to avoid common pitfalls.

\n\n

This article is written for PythonLib — an educational portal where we teach how to write quality code. Let's go!

\n\n

What are mock objects and fixtures?

\n\n

Mock objects: stunt doubles in your code

\n\n

A mock object (or simply mock) is a replacement for a real object that mimics its behavior. You say: "When the sendEmail() method is called, return true." And the test doesn't touch the real email server.

\n\n

In TypeScript, mocks are most often created using Jest or Vitest libraries. They allow you to verify that a method was called, with what arguments, and how many times.

\n\n

Fixtures: prepared data for tests

\n\n

Fixtures are pre-prepared sets of data. For example, a user object, an order list, or an application configuration. Instead of creating them manually every time, you store fixtures in separate files.

\n\n

The combination of mock + fixture is the standard for TypeScript testing in 2024.

\n\n

Practice: writing tests with mocks and fixtures

\n\n

Environment setup

\n\n

For the examples, we'll use Jest (though everything works the same in Vitest). Make sure you have the dependencies installed:

\n\n
npm install --save-dev jest @types/jest ts-jest
\n\n

Add to jest.config.js:

\n\n
module.exports = {\n  preset: 'ts-jest',\n  testEnvironment: 'node',\n};
\n\n

Example 1: Mocking an email sending service

\n\n

Let's say we have a notification service:

\n\n
// notificationService.ts\nexport class EmailService {\n  async sendEmail(to: string, subject: string, body: string): Promise<boolean> {\n    // Real sending — we won't touch it\n    return true;\n  }\n}\n\nexport class NotificationManager {\n  constructor(private emailService: EmailService) {}\n\n  async notifyUser(email: string, message: string): Promise<string> {\n    const result = await this.emailService.sendEmail(email, 'Notification', message);\n    return result ? 'Sent' : 'Error';\n  }\n}
\n\n

Now let's write a test with a mock object:

\n\n
// notificationService.test.ts\nimport { NotificationManager, EmailService } from './notificationService';\n\ndescribe('NotificationManager', () => {\n  it('should send a notification and return "Sent"', async () => {\n    // Create a mock\n    const mockEmailService: jest.Mocked<EmailService> = {\n      sendEmail: jest.fn().mockResolvedValue(true),\n    };\n\n    const manager = new NotificationManager(mockEmailService);\n    const result = await manager.notifyUser('test@test.com', 'Hello!');\n\n    expect(result).toBe('Sent');\n    expect(mockEmailService.sendEmail).toHaveBeenCalledWith(\n      'test@test.com',\n      'Notification',\n      'Hello!'\n    );\n  });\n\n  it('should return "Error" if sending fails', async () => {\n    const mockEmailService: jest.Mocked<EmailService> = {\n      sendEmail: jest.fn().mockResolvedValue(false),\n    };\n\n    const manager = new NotificationManager(mockEmailService);                

Blogs

Book Recommendations