Skip to content
NestJS ns testing 4 min read

Unit Testing Providers

Unit testing a provider means exercising one class — usually a service — with every collaborator it depends on replaced by a controllable fake. NestJS makes this straightforward because providers are wired through dependency injection, so any constructor dependency can be swapped for a mock. The @nestjs/testing package gives you Test.createTestingModule, a lightweight version of @Module() that builds a real DI container around the class under test while letting you decide exactly what gets injected. The result is fast, deterministic tests that never touch a database, network, or filesystem.

The service under test

Consider a CatsService that depends on a repository. In a unit test we do not want the real repository — we only want to verify the service’s own logic and that it calls its collaborators correctly.

import { Injectable } from '@nestjs/common';
import { CatsRepository } from './cats.repository';
import { Cat } from './interfaces/cat.interface';

@Injectable()
export class CatsService {
  constructor(private readonly repo: CatsRepository) {}

  async findOne(id: number): Promise<Cat> {
    const cat = await this.repo.findById(id);
    if (!cat) {
      throw new Error(`Cat ${id} not found`);
    }
    return cat;
  }

  async countAdults(): Promise<number> {
    const cats = await this.repo.findAll();
    return cats.filter((c) => c.age >= 2).length;
  }
}

Building a testing module

A unit test registers the real provider you want to test and supplies a mock for each of its dependencies using the useValue custom-provider syntax. Calling .compile() resolves the graph and returns a TestingModule, from which you pull instances with module.get().

import { Test, TestingModule } from '@nestjs/testing';
import { CatsService } from './cats.service';
import { CatsRepository } from './cats.repository';

describe('CatsService', () => {
  let service: CatsService;
  let repo: jest.Mocked<CatsRepository>;

  beforeEach(async () => {
    const repoMock: Partial<jest.Mocked<CatsRepository>> = {
      findById: jest.fn(),
      findAll: jest.fn(),
    };

    const module: TestingModule = await Test.createTestingModule({
      providers: [
        CatsService,
        { provide: CatsRepository, useValue: repoMock },
      ],
    }).compile();

    service = module.get<CatsService>(CatsService);
    repo = module.get(CatsRepository);
  });

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

Because CatsRepository is provided with useValue, Nest injects the mock object instead of constructing the real class. The jest.Mocked<T> type preserves the original signatures while typing each method as a Jest mock, so autocomplete and the compiler still protect you.

Asserting behavior

With the module compiled you call the service methods directly and assert on both the return value and how the mock was used. Program the mock’s return value with mockResolvedValue (for promises) before invoking the method.

it('returns the cat when it exists', async () => {
  repo.findById.mockResolvedValue({ id: 1, name: 'Felix', age: 3 });

  const cat = await service.findOne(1);

  expect(cat.name).toBe('Felix');
  expect(repo.findById).toHaveBeenCalledWith(1);
  expect(repo.findById).toHaveBeenCalledTimes(1);
});

it('throws when the cat is missing', async () => {
  repo.findById.mockResolvedValue(undefined);

  await expect(service.findOne(99)).rejects.toThrow('Cat 99 not found');
});

it('counts only adult cats', async () => {
  repo.findAll.mockResolvedValue([
    { id: 1, name: 'Felix', age: 3 },
    { id: 2, name: 'Milo', age: 1 },
    { id: 3, name: 'Nala', age: 5 },
  ]);

  expect(await service.countAdults()).toBe(2);
});

Running the suite gives the familiar Jest report:

Output:

 PASS  src/cats/cats.service.spec.ts
  CatsService
    ✓ should be defined (3 ms)
    ✓ returns the cat when it exists (2 ms)
    ✓ throws when the cat is missing (4 ms)
    ✓ counts only adult cats (1 ms)

Test Suites: 1 passed, 1 total
Tests:       4 passed, 4 total
Time:        1.482 s

Ways to supply a mock

Test.createTestingModule accepts the same custom-provider forms as a real module, so you can choose the style that fits the dependency.

ApproachSyntaxWhen to use
Static value{ provide: Repo, useValue: mock }Plain object of jest.fn() stubs
Factory{ provide: Repo, useFactory: () => mock }Mock needs construction logic
Override after build.overrideProvider(Repo).useValue(mock)Reuse an existing module definition
Auto-mock.useMocker(() => ...)Mock every unspecified dependency

The overrideProvider API is useful when importing a feature module wholesale and replacing just one provider:

const module = await Test.createTestingModule({
  imports: [CatsModule],
})
  .overrideProvider(CatsRepository)
  .useValue(repoMock)
  .compile();

Tip: keep jest.clearAllMocks() in a global afterEach (or set clearMocks: true in your Jest config) so call counts and return values do not leak between tests.

Warning: a unit test should never reach a real database or HTTP endpoint. If a test is slow or flaky, an unmocked dependency is usually the cause — verify every constructor argument is provided as a mock.

Best practices

  • Provide a mock for every constructor dependency; an unmocked one signals the test is really an integration test.
  • Type mocks with jest.Mocked<T> so renamed or removed methods break compilation rather than passing silently.
  • Assert on collaborator interactions (toHaveBeenCalledWith) as well as return values to catch wiring bugs.
  • Reset mock state between tests with clearMocks to keep cases independent and order-insensitive.
  • Prefer useValue for simple stubs and reserve overrideProvider for when you must import a full module.
  • Keep one describe block per provider and group related cases, so failures point directly at the responsible unit.
Last updated June 14, 2026
Was this helpful?