Skip to content
NestJS interview 5 min read

Testing & Security Questions

Testing and security are the two areas where interviewers separate engineers who have shipped production NestJS apps from those who have only followed tutorials. Testing questions probe whether you understand the testing pyramid and how Nest’s DI container makes mocking trivial; security questions probe whether you can reason about authentication, authorization, and the classes of vulnerabilities that hit every public API. This page walks through the questions that come up most and the precise, modern answers expected.

How does the testing pyramid map onto a NestJS app?

The testing pyramid says you should have many fast unit tests, fewer integration tests, and a small number of slow end-to-end (E2E) tests. In Nest this maps cleanly:

  • Unit tests isolate a single provider (a service or guard) and mock every collaborator. They run in milliseconds and never touch HTTP, the DB, or the network.
  • Integration tests wire several real providers together using Test.createTestingModule, but may still stub external systems.
  • E2E tests boot the whole application with Test.createTestingModule({ imports: [AppModule] }), expose it through app.getHttpServer(), and drive it with supertest.

The key Nest insight to articulate: because everything is injected, you swap real dependencies for fakes by overriding providers in the testing module, never by reaching for monkey-patching.

How do you unit test a service and mock its dependencies?

Test.createTestingModule builds a lightweight DI container. You supply a custom provider for each dependency so the real one is never instantiated.

import { Test } from '@nestjs/testing';
import { UsersService } from './users.service';
import { UsersRepository } from './users.repository';

describe('UsersService', () => {
  let service: UsersService;
  const repo = { findById: jest.fn() };

  beforeEach(async () => {
    const moduleRef = await Test.createTestingModule({
      providers: [
        UsersService,
        { provide: UsersRepository, useValue: repo },
      ],
    }).compile();

    service = moduleRef.get(UsersService);
  });

  it('returns the user when found', async () => {
    repo.findById.mockResolvedValue({ id: '1', name: 'Ada' });
    await expect(service.getUser('1')).resolves.toEqual({ id: '1', name: 'Ada' });
    expect(repo.findById).toHaveBeenCalledWith('1');
  });
});

Tip: Prefer useValue with a typed mock object over jest.mock() of the module. It keeps the DI graph explicit and survives refactors better.

When would you use overrideProvider instead?

When you already have a module wired up and only want to replace one dependency for a test, chain .overrideProvider(). This is common in integration and E2E tests where you keep most of the real graph but stub the database or an external API client.

const moduleRef = await Test.createTestingModule({ imports: [AppModule] })
  .overrideProvider(PaymentGateway)
  .useValue({ charge: jest.fn().mockResolvedValue({ ok: true }) })
  .compile();

How do you write an E2E test?

Boot the real app, attach supertest, and assert on HTTP responses. Always close the app in afterAll to release ports and DB connections.

import { Test } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import * as request from 'supertest';
import { AppModule } from '../src/app.module';

describe('Users (e2e)', () => {
  let app: INestApplication;

  beforeAll(async () => {
    const moduleRef = await Test.createTestingModule({ imports: [AppModule] }).compile();
    app = moduleRef.createNestApplication();
    await app.init();
  });

  afterAll(async () => app.close());

  it('GET /users returns 200', () =>
    request(app.getHttpServer()).get('/users').expect(200));
});

Output:

 PASS  test/users.e2e-spec.ts
  Users (e2e)
    ✓ GET /users returns 200 (42 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total

What authentication strategies does Nest support?

Authentication answers “who are you?”. Nest leans on Passport through @nestjs/passport, plus @nestjs/jwt for token issuance.

StrategyUse casePackage
LocalUsername/password login endpointpassport-local
JWTStateless API auth via bearer tokenpassport-jwt + @nestjs/jwt
SessionsServer-rendered apps with cookiesexpress-session
OAuth2Social / third-party loginpassport-google-oauth20, etc.

A JWT guard validates the bearer token on every protected route:

import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor() {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      ignoreExpiration: false,
      secretOrKey: process.env.JWT_SECRET,
    });
  }

  validate(payload: { sub: string; role: string }) {
    return { userId: payload.sub, role: payload.role };
  }
}

How do you implement role-based access control (RBAC)?

Authorization answers “what may you do?”. The idiomatic pattern is a custom @Roles() decorator that attaches metadata, plus a guard that reads it with Reflector and compares against the authenticated user.

import { SetMetadata } from '@nestjs/common';
export const Roles = (...roles: string[]) => SetMetadata('roles', roles);
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Reflector } from '@nestjs/core';

@Injectable()
export class RolesGuard implements CanActivate {
  constructor(private reflector: Reflector) {}

  canActivate(ctx: ExecutionContext): boolean {
    const required = this.reflector.getAllAndOverride<string[]>('roles', [
      ctx.getHandler(),
      ctx.getClass(),
    ]);
    if (!required) return true;
    const { user } = ctx.switchToHttp().getRequest();
    return required.includes(user.role);
  }
}

Apply it: @Roles('admin') on a handler, with JwtAuthGuard and RolesGuard registered so the user is resolved before the role check runs.

How do you add rate limiting?

Use the official @nestjs/throttler. It guards against brute-force and denial-of-service abuse by capping requests per IP within a time window.

import { ThrottlerModule } from '@nestjs/throttler';

@Module({
  imports: [ThrottlerModule.forRoot([{ ttl: 60_000, limit: 10 }])],
})
export class AppModule {}

Register ThrottlerGuard globally and override per-route with @Throttle() or skip it with @SkipThrottle().

What common vulnerabilities should a NestJS API defend against?

Warning: Never disable Nest’s ValidationPipe whitelisting in production. Without whitelist: true and forbidNonWhitelisted: true, clients can smuggle extra fields into your DTOs (mass assignment).

  • Injection — use parameterized queries via TypeORM/Prisma; never interpolate user input into raw SQL.
  • XSS — sanitize/escape any user content rendered in responses; set helmet security headers.
  • CSRF — for cookie-based sessions, use csurf or SameSite cookies; stateless JWT APIs are largely immune.
  • CORS — configure app.enableCors() with an explicit origin allowlist, not *, for credentialed requests.
  • Secrets leakage — load secrets from @nestjs/config env, never commit them; strip stack traces from production error responses.

Best Practices

  • Keep unit tests pure: mock every collaborator with useValue and assert behavior, not implementation details.
  • Use overrideProvider for integration/E2E tests so you exercise real wiring while stubbing only the volatile edges.
  • Enable a global ValidationPipe with whitelist and transform to neutralize mass-assignment and type-confusion bugs.
  • Separate authentication (guards/strategies) from authorization (RBAC guard + @Roles) so each is independently testable.
  • Apply helmet, strict CORS, and @nestjs/throttler globally rather than per-route to avoid gaps.
  • Store secrets in environment config and rotate JWT signing keys; set short token TTLs with refresh tokens.
  • Run E2E suites against a disposable database (Testcontainers or an in-memory variant) and always app.close() in teardown.
Last updated June 14, 2026
Was this helpful?