NestJS Unit Testing

Unit testing verifies that one isolated piece of code — a service method, a guard, or a pipe — behaves correctly for a given input. Each test focuses on a single unit and replaces all its dependencies with fakes (mocks). Unit tests run in milliseconds because they touch no database, no network, and no file system.

Testing Tools in NestJS

Tool          | Role
--------------|------------------------------------------
Jest          | Test runner, assertions, mocking (pre-installed)
@nestjs/testing | Creates a testing module with DI support
ts-jest       | Compiles TypeScript for Jest (pre-configured)

The .spec.ts File Convention

Every generated service and controller has a companion .spec.ts file. The CLI creates them automatically:

src/users/
  users.service.ts
  users.service.spec.ts    ← unit tests for UsersService
  users.controller.ts
  users.controller.spec.ts ← unit tests for UsersController

Testing a Service

// users.service.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { UsersService } from './users.service';
import { getRepositoryToken } from '@nestjs/typeorm';
import { User } from './user.entity';

describe('UsersService', () => {
  let service: UsersService;

  const mockUserRepository = {
    find: jest.fn(),
    findOne: jest.fn(),
    create: jest.fn(),
    save: jest.fn(),
    delete: jest.fn(),
  };

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        UsersService,
        {
          provide: getRepositoryToken(User),
          useValue: mockUserRepository,
        },
      ],
    }).compile();

    service = module.get<UsersService>(UsersService);
  });

  afterEach(() => {
    jest.clearAllMocks();
  });

  it('should be defined', () => {
    expect(service).toBeDefined();
  });

  describe('findAll', () => {
    it('returns an array of users', async () => {
      const users = [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }];
      mockUserRepository.find.mockResolvedValue(users);

      const result = await service.findAll();

      expect(result).toEqual(users);
      expect(mockUserRepository.find).toHaveBeenCalledTimes(1);
    });
  });

  describe('findOne', () => {
    it('returns the user when found', async () => {
      const user = { id: 1, name: 'Alice' };
      mockUserRepository.findOne.mockResolvedValue(user);

      const result = await service.findOne(1);
      expect(result).toEqual(user);
    });

    it('throws NotFoundException when user is not found', async () => {
      mockUserRepository.findOne.mockResolvedValue(null);
      await expect(service.findOne(999)).rejects.toThrow('User #999 not found');
    });
  });
});

Testing a Controller

// users.controller.spec.ts
describe('UsersController', () => {
  let controller: UsersController;

  const mockUsersService = {
    findAll: jest.fn().mockResolvedValue([{ id: 1, name: 'Alice' }]),
    findOne: jest.fn().mockResolvedValue({ id: 1, name: 'Alice' }),
    create: jest.fn().mockResolvedValue({ id: 1, name: 'Alice' }),
  };

  beforeEach(async () => {
    const module = await Test.createTestingModule({
      controllers: [UsersController],
      providers: [{ provide: UsersService, useValue: mockUsersService }],
    }).compile();

    controller = module.get<UsersController>(UsersController);
  });

  it('findAll returns array of users', async () => {
    const result = await controller.findAll();
    expect(result).toHaveLength(1);
    expect(mockUsersService.findAll).toHaveBeenCalled();
  });

  it('findOne calls service with correct id', async () => {
    await controller.findOne('1');
    expect(mockUsersService.findOne).toHaveBeenCalledWith(1);
  });
});

Jest Assertion Reference

expect(value).toBe(exact)          // strict equality (===)
expect(value).toEqual(obj)         // deep equality (object contents)
expect(value).toBeDefined()        // not undefined
expect(value).toBeNull()           // is null
expect(value).toBeTruthy()         // any truthy value
expect(value).toHaveLength(n)      // array/string length
expect(fn).toHaveBeenCalled()      // mock was called
expect(fn).toHaveBeenCalledWith(a) // mock was called with argument
expect(fn).toHaveBeenCalledTimes(n)// mock called exactly n times
expect(promise).rejects.toThrow()  // async function throws

Running Tests

npm run test              ← run all unit tests once
npm run test:watch        ← re-run tests on file save
npm run test:cov          ← run with coverage report

Unit Test Diagram

Real Application:
  UsersController → UsersService → UserRepository → PostgreSQL

Unit Test (service):
  UsersService → mockUserRepository (fake, returns test data)
  No database. No HTTP. Runs in milliseconds.

Unit Test (controller):
  UsersController → mockUsersService (fake, returns test data)
  No service logic executed. Tests only controller behavior.

Unit tests give you confidence that each piece of your application works correctly in isolation. When a test fails after you make a change, you know immediately which unit broke and why — without manually testing the entire application. A service with 10 well-written unit tests is easier to refactor safely than one with no tests at all.

Leave a Comment

Your email address will not be published. Required fields are marked *