How to mock a service in NestJS unit tests

Updated: January 1, 2024 By: Guest Contributor Post a comment

Testing services in NestJS applications can be streamlined through effective mocking strategies, allowing for isolated and reliable unit tests.

Introduction

Uncovering how to mock services within NestJS unit tests is a crucial step to ensure that your modules are being tested in isolation, focusing solely on the behavior of the unit in question. This tutorial walks you through the basic to advanced procedures of mocking services, using Jest as the primary testing framework complemented by Jest’s powerful mocking functionalities, tailored for NestJS applications.

Setting Up the Testing Environment

Before delving into the mocking techniques, it is vital to establish a testing foundation. Ensure your NestJS project has Jest installed and properly configured. Verify that the jest.config.js and tsconfig.spec.json files are set up in accordance with the project requirements.

// jest.config.js
module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'node',
};
// tsconfig.spec.json
{
  "compilerOptions": {
    "module": "commonjs",
    "target": "es2017",
    "noImplicitAny": false,
    "outDir": "./dist",
    "baseUrl": ".",
    "incremental": true
  }
}

Basic Mocking with Jest

Start by writing a simple NestJS service test. Create a mock of the service that will replace the real implementation in the tests. Jest’s jest.fn() and jest.mock() functions are commonly used to create these mock implementations.

// Example of mocking a basic service
const myServiceMock = {
  myMethod: jest.fn().mockReturnValue('mock value'),
};

test('should call myMethod', () => {
  const result = myServiceMock.myMethod('input');
  expect(result).toBe('mock value');
  expect(myServiceMock.myMethod).toHaveBeenCalledWith('input');
});

Integrating Mocks into NestJS Modules

When you begin to test NestJS modules consuming these services, integrate the mocking approach within the module’s testing suite by providing the mock as a replacement for the actual service provider.

import { Test } from '@nestjs/testing';
import { MyModule } from './my.module';
import { MyService } from './my.service';

describe('MyModule', () => {
  let myService: MyService;

  beforeEach(async () => {
    const moduleRef = await Test.createTestingModule({
      imports: [MyModule],
    })
      .overrideProvider(MyService)
      .useValue(myServiceMock)
      .compile();

    myService = moduleRef.get(MyService);
  });

  it('should use the mocked service', async () => {
    const response = await myService.myMethod('test');
    expect(response).toBe('mock value');
  });
});

Advanced Mock Implementations

For more complex cases, such as mocking asynchronous methods or methods that return Promises, your mocks will need to leverage Jest’s mockResolvedValue() or mockRejectedValue() for creating promise-based mocks.

// Async service method mock
const asyncServiceMock = {
  asyncMethod: jest.fn().mockResolvedValue('async mock value'),
};

In cases where you need to reset or change the behavior of a mock between tests, use Jest’s mockReset() or mockImplementation() functions. This ensures that no state or behavior leakage occurs between tests.

beforeEach(() => {
  myServiceMock.myMethod.mockReset();
});

it('should alter mock implementation', () => {
  myServiceMock.myMethod.mockImplementation(() => 'new mock value');
  expect(myServiceMock.myMethod('input')).toBe('new mock value');
});

Mocking Dependencies Deeply Nested Within Modules

Some NestJS services might have multiple dependencies or compose several layers of services. For deeper nested dependencies, use more intricate mocking strategies like manual mocking or automatic mocks with jest.mock('../path/to/service').

// Manual mock of a deeply nested service
jest.mock('../path/to/deepNestedService', () => {
  return {
    DeepNestedService: jest.fn().mockImplementation(() => {
      return {
        deepMethod: jest.fn().mockReturnValue('deep mock value'),
      };
    })
  };
});

Testing Interactions and Integrations with Mocks

Moving beyond simple return values, sometimes the focus is on how components interact with each other. You can use spies and manual implementations to assert function call orders, or to ensure certain conditions are met before proceeding in your test scenarios.

// Inspecting interaction orders with mocks
const interactionMock = {
  firstMethod: jest.fn(() => 'first'),
  secondMethod: jest.fn(() => 'second'),
};

it('should assert method call order', () => {
  interactionMock.firstMethod();
  interactionMock.secondMethod();
  expect(interactionMock.firstMethod).toHaveBeenCalledBefore(interactionMock.secondMethod);
});

Throughout this tutorial, we also discussed strategies for mocking exceptions, third-party modules, and external APIs, establishing an exhaustive approach towards mocking services in NestJS unit tests.

Conclusion

The knowledge you’ve gained from this guidance on mocking services within NestJS is foundational for writing unit tests that are robust and maintainable. Mastering these techniques enhances testing reliability, helping address various intricacies such as asynchronous behavior, dependency hierarchies, and interactions between service layers. These methodologies empower you to build a more fault-tolerant and resilient application architecture.