open atlas
↑ Back to track
NestJS, zero to senior NEST · 06 · 01

Unit testing providers with the testing module

Unit-test a Nest provider by building an isolated DI container with Test.createTestingModule, mocking every dependency at its boundary (the repository, the HTTP client) via useValue or overrideProvider, then asserting behavior with jest.fn().

NEST Middle ◷ 15 min
Level
FoundationsJuniorMiddleSenior
Already know this unit? Take a 1-minute quick check →

CI for UsersService was green for months, then started taking nine minutes and failing one run in five. You open the “unit” test and find it boots the full AppModule, opens a real Postgres connection, and hits a staging mail API to “verify” the welcome email. The flakiness is a connection-pool timeout; the nine minutes is everyone’s “unit” suite doing the same thing. None of it tests the thing you care about — the logic of UsersService.register: does it hash the password, reject a duplicate email, and call save exactly once? You don’t need a database to answer that. You need a testing module with the repository mocked at the boundary.

The testing module is a throwaway DI container

When you see a nine-minute “unit” suite, ask yourself: what does it actually need a database for? Usually nothing — and the testing module is how you get that time back.

Test.createTestingModule({ providers: [...] }) builds a fresh, isolated Nest dependency-injection container that contains only what you list. Calling .compile() resolves the graph and hands you a TestingModule; module.get(Token) pulls a singleton instance out of it — your unit under test, already wired. Because you control the providers array, you decide exactly which dependencies are real and which are doubles. List the service under test as a real class, and supply every dependency it injects as a mock.

The cleanest way to supply a mock is a custom provider with useValue: same injection token, fake implementation. A jest.fn() per method gives you a spy you can program and assert against.

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

describe('UsersService', () => {
  let service: UsersService;
  let repo: jest.Mocked<Repository<User>>;

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        UsersService, // real — this is the unit under test
        {
          provide: getRepositoryToken(User), // the token Nest injects for @InjectRepository(User)
          useValue: {
            findOne: jest.fn(),
            create: jest.fn((dto) => dto),
            save: jest.fn(),
          },
        },
      ],
    }).compile();

    service = module.get(UsersService);
    repo = module.get(getRepositoryToken(User));
  });

  it('rejects a duplicate email without saving', async () => {
    repo.findOne.mockResolvedValue({ id: 1, email: 'a@b.io' } as User);

    await expect(service.register({ email: 'a@b.io', password: 'pw' }))
      .rejects.toThrow('email already in use');

    expect(repo.save).not.toHaveBeenCalled(); // behavior, not implementation detail
  });

  it('saves a new user exactly once', async () => {
    repo.findOne.mockResolvedValue(null);
    repo.save.mockResolvedValue({ id: 7, email: 'new@b.io' } as User);

    const result = await service.register({ email: 'new@b.io', password: 'pw' });

    expect(repo.save).toHaveBeenCalledTimes(1);
    expect(repo.save).toHaveBeenCalledWith(
      expect.objectContaining({ email: 'new@b.io' }),
    );
    expect(result.id).toBe(7);
  });
});

Two details earn their keep. The token for a TypeORM repository is not the entity class — it’s getRepositoryToken(User), the synthetic token @InjectRepository(User) resolves to. Provide a value for that token and the service gets your mock. And the assertions check behavior: “didn’t save on a duplicate”, “saved once with this email”. They never reach into the service’s private fields. Tests coupled to behavior survive refactors; tests coupled to implementation break the moment you rename a variable.

Mock at the boundary, and only there

The same pattern covers any external collaborator. A JwtService, an HTTP client wrapping a payment API, a mailer — each is a seam between your logic and the outside world, and each gets a useValue double keyed by its token. What you do not mock is the thing under test (you’d be testing nothing) or pure internal helpers like a value object or a password-strength function — mocking those just freezes the implementation in place and tests your mock instead of your code. Mock the I/O boundary; let the logic run.

For a service that talks to both a repository and an external client, that’s two doubles:

const module = await Test.createTestingModule({
  providers: [
    OrdersService,
    { provide: getRepositoryToken(Order), useValue: { findOne: jest.fn(), save: jest.fn() } },
    { provide: PaymentClient, useValue: { charge: jest.fn().mockResolvedValue({ ok: true }) } },
  ],
}).compile();

Now OrdersService.checkout can be driven through every branch — payment declines, repository conflicts, happy path — in milliseconds, deterministically, with no network and no DB.

overrideProvider when the module is real

useValue works when you assemble the providers yourself. Sometimes you’d rather import the real feature module — to keep its wiring honest — and swap out just the leaf dependencies. That’s overrideProvider(TOKEN):

const module = await Test.createTestingModule({
  imports: [UsersModule], // the real module, real wiring
})
  .overrideProvider(getRepositoryToken(User))
  .useValue({ findOne: jest.fn(), save: jest.fn() })
  .compile();

After .useValue(...), you can instead chain .useClass(FakeRepo) (a stub class Nest instantiates with its own deps) or .useFactory({ factory: () => mock }) (when the double needs construction logic). And if you’d rather not name every double, .useMocker((token) => ...) auto-stubs everything not explicitly provided — handy for a service with a long dependency list, where you only care about one or two collaborators and want the rest to be inert no-op mocks.

Two ways to supply a dependency, side by side

Way to supply the depHowTouches DB / network?Use when
Real implementationList the real provider; let Nest wire itYes — that’s the problemIntegration/e2e test, not a unit test
useValue mock{ provide: TOKEN, useValue: { m: jest.fn() } }NoYou assemble the providers array yourself
overrideProvider.overrideProvider(TOKEN).useValue/useClass/useFactoryNo (for the overridden dep)You import the real module and swap one leaf
useMocker.useMocker((token) => mock)NoAuto-stub a long dep list; care about one or two
Why this works

Why is getRepositoryToken(User) the token, and not Repository or User? @InjectRepository(User) doesn’t inject “a Repository” — many repositories share that class, one per entity — so Nest needs a per-entity token to tell them apart. getRepositoryToken(User) returns exactly that synthetic token (a string like UserRepository). The @nestjs/typeorm module registers the real repository under it at runtime; in a test you register your mock under the same token. Provide a useValue keyed by Repository and nothing injects it — the service still gets the real repo (or a missing-dependency error). Always key the double by the token the decorator resolves to.

Pick the best fit

You need to unit-test UsersService.register's logic — hash the password, reject a duplicate, save once — without a database, fast and deterministic. How do you supply its UserRepository dependency?

Quiz

In a UsersService unit test, which token must your repository mock be provided under?

Quiz

You import the real UsersModule in a test but want the repository to be a mock instead of hitting Postgres. Which approach fits?

Recall before you leave
  1. 01
    Walk through unit-testing UsersService against a mocked TypeORM repository: what does each of createTestingModule, the providers array, getRepositoryToken, compile, and module.get do?
  2. 02
    What are the ways to supply a dependency in a Nest unit test, and what is the rule for what to mock vs not mock?
Recap

A unit test for a Nest provider builds a throwaway DI container with Test.createTestingModule({ providers: […] }) and .compile(), then module.get(Service) resolves the service under test already wired to doubles you control. List the service as a real provider and supply each dependency it injects as a mock — the cleanest being a custom provider { provide: TOKEN, useValue: { method: jest.fn() } }. For a TypeORM repository the token is getRepositoryToken(User), the synthetic per-entity token @InjectRepository(User) resolves to — not the Repository class or the entity. The same pattern covers a JwtService or an HTTP/payment client: each is an I/O boundary that gets a useValue double. If you’d rather import the real module to keep its wiring honest, .overrideProvider(TOKEN) swaps one leaf with .useValue / .useClass / .useFactory, and .useMocker auto-stubs everything not explicitly provided. With every dependency mocked there’s no DB and no network, so the test is fast and deterministic — and you assert behavior (toHaveBeenCalledWith, not.toHaveBeenCalled, mockResolvedValue to set up state) rather than implementation detail, so the test survives refactors. What you never mock is the unit under test itself or its pure internal helpers; mock the boundary, let the logic run. Now when you see a flaky, nine-minute suite labelled “unit”, you’ll know to ask: is there a real DB in there — and if so, reach for createTestingModule with mocked dependencies at the boundary.

Practice

Start at the top. Tasks go easiest → hardest: recall a fact, apply it to a case, then a senior-level stretch. Open one, attempt it, then reveal.

recallapplystretch0 of 5 done
Connected lessons

Something unclear?

Ask a question about this lesson. Questions are anonymous and go straight to the author to make the lesson better.

Apply this

Put this lesson to work on a real build.

shortcuts expand
search
K
prev piece
k
next piece
j
cycle tier
t
this menu
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.