NestJS Mocking

Mocking replaces real dependencies with controlled fake versions during testing. When a service calls a database, sends an email, or calls an external API, you do not want those real operations to run during tests. A mock intercepts those calls and returns predictable test data instead. Mocking is what makes unit tests fast, reliable, and isolated from external systems.

Why Mocking Is Necessary

Without Mocking:
  UsersService.findOne(1)
    → calls UserRepository.findOne()
    → queries PostgreSQL
    → requires a running database with test data

  Problems: slow, fragile, depends on external state

With Mocking:
  UsersService.findOne(1)
    → calls mockRepository.findOne() (our fake)
    → returns { id: 1, name: 'Alice' } immediately

  Benefits: fast, predictable, no external dependencies

Jest Mock Functions

Jest provides jest.fn() to create a mock function. A mock function records calls and returns configured values:

const mockFn = jest.fn();

// Configure what the mock returns
mockFn.mockReturnValue('hello');          // synchronous
mockFn.mockResolvedValue({ id: 1 });      // async (Promise)
mockFn.mockRejectedValue(new Error('fail')); // async (rejected Promise)

// Call the mock
const result = await mockFn();
// result = { id: 1 }

// Assert it was called
expect(mockFn).toHaveBeenCalled();
expect(mockFn).toHaveBeenCalledWith(someArg);
expect(mockFn).toHaveBeenCalledTimes(1);

Mocking a Repository

const mockUserRepository = {
  find: jest.fn().mockResolvedValue([]),
  findOne: jest.fn().mockResolvedValue(null),
  create: jest.fn().mockImplementation(dto => dto),
  save: jest.fn().mockImplementation(entity => Promise.resolve({ id: 1, ...entity })),
  delete: jest.fn().mockResolvedValue({ affected: 1 }),
};

// In the test module
providers: [
  UsersService,
  {
    provide: getRepositoryToken(User),
    useValue: mockUserRepository,
  },
],

Mocking an External Service

// EmailService sends real emails — mock it in tests
const mockEmailService = {
  sendWelcomeEmail: jest.fn().mockResolvedValue(true),
  sendPasswordReset: jest.fn().mockResolvedValue(true),
};

// AuthService tests — no real emails sent
providers: [
  AuthService,
  { provide: EmailService, useValue: mockEmailService },
  { provide: getRepositoryToken(User), useValue: mockUserRepository },
],

mockReturnValueOnce for Sequential Tests

When the same mock is called multiple times and should return different values each time, use mockResolvedValueOnce:

mockUserRepository.findOne
  .mockResolvedValueOnce(null)           // first call returns null
  .mockResolvedValueOnce({ id: 1 });     // second call returns user

// First test scenario: user not found
const result1 = await service.findOne(999);  // null

// Second test scenario: user found
const result2 = await service.findOne(1);    // { id: 1 }

Mocking Thrown Errors

// Test that the service throws NotFoundException when user is missing
mockUserRepository.findOne.mockResolvedValue(null);

await expect(service.findOne(999))
  .rejects
  .toThrow(NotFoundException);

// Test that the service handles a database error
mockUserRepository.find.mockRejectedValue(new Error('DB connection lost'));

await expect(service.findAll())
  .rejects
  .toThrow('DB connection lost');

Spying on Methods

A spy watches a real method without replacing it, letting you assert it was called while still executing the real code:

const spy = jest.spyOn(service, 'findOne');

await service.update(1, { name: 'Alice Updated' });

expect(spy).toHaveBeenCalledWith(1);

// Restore original behavior after test
spy.mockRestore();

Clearing Mocks Between Tests

afterEach(() => {
  jest.clearAllMocks();   // clears call counts and return values
});

// Or in jest.config.js for all test files:
clearMocks: true

createMock Utility Pattern

A helper function creates consistent mock objects for a service:

function createMockUsersService(): Partial<UsersService> {
  return {
    findAll: jest.fn().mockResolvedValue([]),
    findOne: jest.fn().mockResolvedValue({ id: 1, name: 'Alice' }),
    create: jest.fn().mockResolvedValue({ id: 1, name: 'Alice' }),
    update: jest.fn().mockResolvedValue({ id: 1, name: 'Alice Updated' }),
    remove: jest.fn().mockResolvedValue(undefined),
  };
}

// Reuse in any test that needs UsersService mocked
providers: [
  UsersController,
  { provide: UsersService, useValue: createMockUsersService() },
],

Mock Scope Diagram

Test file:
  mockRepository.findOne → returns { id: 1, name: 'Alice' }
  mockEmailService.send  → returns true

UsersService (real code, runs normally):
  findOne(1) → calls mockRepository.findOne(1) → gets { id: 1, name: 'Alice' }
  create(dto) → calls mockRepository.save() → gets saved entity
             → calls mockEmailService.send() → no real email sent

Everything the service does is real.
Everything the service CALLS is mocked.

Good mocks are specific enough to make tests meaningful but simple enough to not become maintenance burdens themselves. Mock at the boundary — the point where your code calls something external (database, third-party API, email provider). Let all your own code run for real. This strategy gives you genuine confidence in your business logic while keeping tests deterministic and fast.

Leave a Comment

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