Menu
CoddyTech

NestJS Testing Interview Questions

Unit tests with the testing module, mocks and overrides, e2e tests, Jest and Vitest.

10 questions
  1. How do you write a unit test for a NestJS service?

    Build a small module with Test.createTestingModule from @nestjs/testing, give it the class under test plus fakes for its dependencies, compile it, and get the instance.

    describe('OrdersService', () => {
      let service: OrdersService;
      const payments = { charge: jest.fn().mockResolvedValue('ch_1') };
    
      beforeEach(async () => {
        const moduleRef = await Test.createTestingModule({
          providers: [OrdersService, { provide: PaymentsService, useValue: payments }],
        }).compile();
        service = moduleRef.get(OrdersService);
      });
    
      it('charges the card when an order is placed', async () => {
        await service.place({ total: 500 });
        expect(payments.charge).toHaveBeenCalledWith(500);
      });
    });
    

    The container wires OrdersService exactly as in production, so the test also catches a missing dependency.

  2. How do you replace a real dependency with a mock in a NestJS test?

    Either list the fake directly in providers ({ provide: X, useValue: fake }), or import the real module and override the one provider:

    const moduleRef = await Test.createTestingModule({ imports: [OrdersModule] })
      .overrideProvider(PaymentsService)
      .useValue({ charge: vi.fn().mockResolvedValue('ch_test') })
      .overrideGuard(AuthGuard)
      .useValue({ canActivate: () => true })
      .compile();
    

    overrideProvider keeps the module's real wiring and swaps one piece, which suits integration tests. There are matching overrideGuard, overrideInterceptor, overridePipe, overrideFilter and overrideModule. For a TypeORM repository, the token is getRepositoryToken(User).

  3. Jest or Vitest for NestJS tests?

    Since NestJS 12 the CLI generates Vitest for new ES module projects and keeps Jest for CommonJS projects, so both are standard.

    The Nest-specific catch with Vitest: it transpiles with esbuild, which does not emit decorator metadata, so constructor injection by type breaks in tests. Use SWC instead:

    // vitest.config.ts
    import swc from 'unplugin-swc';
    import { defineConfig } from 'vitest/config';
    
    export default defineConfig({
      test: { globals: true },
      plugins: [swc.vite()],
    });
    

    The test code barely changes between them: jest.fn() becomes vi.fn(). Jest on a large CommonJS codebase is often slow because of ts-jest type checking; @swc/jest fixes most of that.

  4. How do you write an end-to-end test for a NestJS API?

    Compile the real AppModule (optionally overriding external services), create an app, call init(), and send HTTP requests with supertest.

    describe('Orders (e2e)', () => {
      let app: INestApplication;
    
      beforeAll(async () => {
        const moduleRef = await Test.createTestingModule({ imports: [AppModule] })
          .overrideProvider(PaymentsService).useValue({ charge: async () => 'ch_1' })
          .compile();
        app = moduleRef.createNestApplication();
        app.useGlobalPipes(new ValidationPipe({ whitelist: true }));
        await app.init();
      });
    
      afterAll(() => app.close());
    
      it('rejects an empty order', () =>
        request(app.getHttpServer()).post('/orders').send({}).expect(400));
    });
    

    Settings applied in main.ts (global pipes, prefix) are not part of the module, so repeat them here or register them as APP_* providers.

  5. What is the difference between unit tests and e2e tests in NestJS?

    A unit test checks one class with its dependencies faked. An end-to-end test boots the app and sends HTTP requests, so it also checks routing, guards, pipes and filters.

    UnitE2E
    BuildsOne class via Test.createTestingModuleThe app via createNestApplication
    CatchesLogic bugsWiring, validation, auth, status codes
    Default locationsrc/**/*.spec.tstest/*.e2e-spec.ts

    A good mix: unit tests for services with real logic, and e2e tests for each endpoint's success path and main failure codes.

  6. How do you unit test a guard?

    Instantiate it with its dependencies and call canActivate with a minimal fake ExecutionContext.

    const contextFor = (headers: Record<string, string>) =>
      ({
        getHandler: () => ReportsController.prototype.all,
        getClass: () => ReportsController,
        switchToHttp: () => ({ getRequest: () => ({ headers }) }),
      }) as unknown as ExecutionContext;
    
    it('blocks users without the admin role', () => {
      const guard = new RolesGuard(new Reflector());
      expect(guard.canActivate(contextFor({ 'x-role': 'viewer' }))).toBe(false);
      expect(guard.canActivate(contextFor({ 'x-role': 'admin' }))).toBe(true);
    });
    

    Pointing getHandler at the real method makes the guard read the real @Roles() metadata, so the test breaks if someone removes the decorator. Check the 403 status itself in an e2e test.

  7. How do you unit test an interceptor?

    Call intercept() with a fake context and a CallHandler whose handle() returns an Observable of the handler's result, then read the Observable.

    it('wraps the result in an envelope', async () => {
      const interceptor = new EnvelopeInterceptor();
      const ctx = {
        switchToHttp: () => ({ getRequest: () => ({ url: '/greet' }) }),
      } as unknown as ExecutionContext;
      const next: CallHandler = { handle: () => of('Hello') };
    
      const result = await firstValueFrom(interceptor.intercept(ctx, next));
      expect(result).toEqual({ data: 'Hello', path: '/greet', version: 1 });
    });
    

    of() and firstValueFrom() come from rxjs. For time-based interceptors such as timeout(), use fake timers or RxJS's TestScheduler.

  8. How do you test a request-scoped provider?

    moduleRef.get() throws for scoped providers. Use resolve(), and pass a context id to get the same instance across several resolves:

    const contextId = ContextIdFactory.create();
    
    const a = await moduleRef.resolve(TenantContext, contextId);
    const b = await moduleRef.resolve(TenantContext, contextId);
    expect(a).toBe(b); // same request, same instance
    
    const other = await moduleRef.resolve(TenantContext);
    expect(other).not.toBe(a); // no id given: a new context
    

    To fake the request object itself, register it under the REQUEST token for that context with moduleRef.registerRequestByContextId(fakeRequest, contextId).

  9. Should NestJS integration tests use a real database or mocks?

    Use a real database of the same engine for repository and e2e tests, and mocks for unit tests of business logic. Mocking the ORM tests your assumptions about SQL, not the SQL.

    Common setups:

    • A disposable database per test run, started with Docker or Testcontainers, with migrations applied first.
    • Isolation per test: truncate tables between tests, or wrap each test in a transaction that is rolled back.
    • Never SQLite in tests when production is PostgreSQL: types, constraints and JSON behavior differ, so tests pass while production fails.
  10. How do you mock a TypeORM repository in a unit test?

    Provide a fake under the token @InjectRepository uses, which getRepositoryToken(Entity) returns:

    const usersRepo = {
      findOneBy: vi.fn(),
      save: vi.fn((u) => Promise.resolve({ id: 'u1', ...u })),
    };
    
    const moduleRef = await Test.createTestingModule({
      providers: [
        UsersService,
        { provide: getRepositoryToken(User), useValue: usersRepo },
      ],
    }).compile();
    
    it('refuses a duplicate email', async () => {
      usersRepo.findOneBy.mockResolvedValue({ id: 'u0' });
      await expect(moduleRef.get(UsersService).register({ email: 'a@b.c' }))
        .rejects.toThrow(ConflictException);
    });
    

    Fake only the methods the service calls. For Mongoose, the equivalent is getModelToken(Product.name).

Coddy programming languages illustration

Learn to code with Coddy

Get started