Mocking Objects in TypeScript Tests with Jest

Ganesh
5 min readMay 27, 2023
Software Developer

Mocking is a powerful technique for isolating code being tested by replacing dependencies with mock objects that simulate the behavior of real dependencies. In this blog post, we will explore how to use Jest, a popular testing framework, to create mock objects in TypeScript tests.

For more blogs, Visit https://infinity-creator.blogspot.com/

Setting up Jest for Mocking in TypeScript

Before we can start mocking objects with Jest, we need to set up Jest to work with TypeScript. Jest provides excellent TypeScript support through the ts-jest package, which can be installed via npm:

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

Once installed, we need to configure Jest to use ts-jest. We can do this by adding the following section to our jest.config.js file:

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

This tells Jest to use ts-jest as its preset, and to use the Node.js environment for tests.

Creating Mock Objects with Jest

Jest provides several functions for creating mock objects, including jest.fn() for creating a mock function, and jest.mock() for creating a mock object. Let's take a look at each of these functions in more detail.

jest.fn()

The jest.fn() function creates a mock function that can be used to replace a real function in a dependency. Mock functions are often used to simulate the behavior of real functions, such as returning predefined values or throwing errors.

Here’s an example of using jest.fn() to create a mock function:

function add(a: number, b: number): number {
return a + b;
}

test('add function', () => {
const mockAdd = jest.fn((a, b) => a + b);
expect(mockAdd(2, 3)).toBe(5);
});

In this example, we’ve created a mock function called mockAdd that simulates the behavior of the real add() function. We can then use mockAdd() in our tests instead of add().

jest.mock()

The jest.mock() function creates a mock object that can be used to replace a real object in a dependency. Mock objects are often used to simulate the behavior of real objects, such as returning predefined values or throwing errors.

Here’s an example of using jest.mock() to create a mock object:

class UserService {
async getUser(id: number): Promise<{ id: number; name: string }> {
// fetch user from database
}
}

test('get user', async () => {
const mockGetUser = jest.fn(() => Promise.resolve({ id: 1, name: 'John Doe' }));
jest.mock('./UserService', () => {
return {
UserService: jest.fn().mockImplementation(() => ({
getUser: mockGetUser,
})),
};
});

const userService = new UserService();
const user = await userService.getUser(1);
expect(user).toEqual({ id: 1, name: 'John Doe' });
});

In this example, we’ve created a mock object for the UserService class that simulates the behavior of the real object. We use jest.mock() to tell Jest to use our mock object instead of the real UserService class. We can then use userService in our tests just like we would use the real object.

Mocking Dependencies in TypeScript

Now that we know how to create mock objects with Jest, let’s take a look at how to use mock objects to mock dependencies in TypeScript tests. Here are some examples:

Mocking a database connection

Let’s say we have a UserRepository class that depends on a database connection. We can use a mock object to simulate the behavior of the database connection and test the UserRepository class in isolation.

class UserRepository {
constructor(private connection: Connection) {}

async getUser(id: number): Promise<{ id: number; name: string }> {
// fetch user from database
}
}

test('get user', async () => {
const mockConnection = {
query: jest.fn(() => Promise.resolve([{ id: 1, name: 'John Doe' }])),
};
const userRepository = new UserRepository(mockConnection);
const user = await userRepository.getUser(1);
expect(user).toEqual({ id: 1, name: 'John Doe' });
});

In this example, we’ve created a mock object called mockConnection that simulates the behavior of a real database connection. We use mockConnection to create a new UserRepository instance in our tests.

Mocking a third-party API

Let’s say we have a WeatherService class that depends on a third-party API. We can use a mock object to simulate the behavior of the API and test the WeatherService class in isolation.

class WeatherService {
constructor(private api: AxiosInstance) {}

async getTemperature(city: string): Promise<number> {
const response = await this.api.get(`/weather/${city}`);
return response.data.temperature;
}
}

test('get temperature', async () => {
const mockApi = {
get: jest.fn(() => Promise.resolve({ data: { temperature: 25 } })),
};
const weatherService = new WeatherService(mockApi);
const temperature = await weatherService.getTemperature('London');
expect(temperature).toBe(25);
});

In this example, we’ve created a mock object called mockApi that simulates the behavior of a real API. We use mockApi to create a new WeatherService instance in our tests.

Mocking a service class

Let’s say we have a PaymentService class that depends on another service class called PaymentGatewayService. We can use a mock object to simulate the behavior of PaymentGatewayService and test the PaymentService class in isolation.

class PaymentGatewayService {
async processPayment(amount: number): Promise<boolean> {
// process payment
}
}

class PaymentService {
constructor(private gateway: PaymentGatewayService) {}

async processPayment(amount: number): Promise<boolean> {
return this.gateway.processPayment(amount);
}
}

test('process payment', async () => {
const mockGateway = {
processPayment: jest.fn(() => Promise.resolve(true)),
};
const paymentService = new PaymentService(mockGateway);
const result = await paymentService.processPayment(100);
expect(result).toBe(true);
});

In this example, we’ve created a mock object called mockGateway that simulates the behavior of a real PaymentGatewayService object. We use mockGateway to create a new PaymentService instance in our tests.

Best Practices for Mocking in TypeScript Tests

When mocking objects in TypeScript tests, it’s important to follow some best practices to ensure our tests are reliable and maintainable. Here are some tips:

  • Don’t mock the same object in multiple tests. This can lead to test dependencies and make it difficult to isolate failures.
  • Use jest.restoreAllMocks() to clean up mocks after each test. This ensures that mock behavior doesn't leak into other tests.
  • Use jest.spyOn() to create partial mocks of objects. This allows us to keep the original behavior of an object while selectively mocking certain methods or properties.
  • Use jest.fn() to create full mocks of objects. This is useful when we want to completely replace the behavior of an object with our own implementation.
  • Use jest.mock() to automatically mock objects based on their module dependencies. This is useful when we want to mock entire modules without manually creating mock objects.

It’s also important to consider the tradeoffs of mocking objects in tests. While mocking can help us isolate code for testing, it can also make our tests more complex and difficult to maintain. It’s important to strike a balance between isolation and simplicity when writing tests.

Conclusion

Mocking objects is an essential technique for writing reliable and maintainable tests in TypeScript. By using mock objects to isolate code for testing, we can ensure that our tests are reliable and independent of external dependencies. When using the Jest framework in TypeScript, we have access to a powerful mocking API that allows us to create mock objects with ease. By following best practices for mocking in TypeScript tests, we can ensure that our tests are easy to write, read, and maintain.

Visit my blog https://infinity-creator.blogspot.com/ for similar posts.

--

--