nestjs-testing

Master testing in NestJS for building reliable applications with comprehensive unit, integration, and end-to-end tests.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "nestjs-testing" with this command: npx skills add thebushidocollective/han/thebushidocollective-han-nestjs-testing

NestJS Testing

Master testing in NestJS for building reliable applications with comprehensive unit, integration, and end-to-end tests.

Unit Testing Setup

Creating and configuring test modules with TestingModule.

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

describe('UserService', () => { let service: UserService; let module: TestingModule;

beforeEach(async () => { module = await Test.createTestingModule({ providers: [ UserService, { provide: getRepositoryToken(User), useValue: { find: jest.fn(), findOne: jest.fn(), save: jest.fn(), create: jest.fn(), delete: jest.fn(), }, }, ], }).compile();

service = module.get<UserService>(UserService);

});

afterEach(async () => { await module.close(); });

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

it('should find all users', async () => { const users = [{ id: 1, name: 'John' }]; jest.spyOn(service, 'findAll').mockResolvedValue(users);

const result = await service.findAll();
expect(result).toEqual(users);
expect(service.findAll).toHaveBeenCalled();

}); });

// Custom provider testing describe('ConfigService', () => { let service: ConfigService;

beforeEach(async () => { const module = await Test.createTestingModule({ providers: [ { provide: ConfigService, useFactory: () => { return new ConfigService('.env.test'); }, }, ], }).compile();

service = module.get<ConfigService>(ConfigService);

});

it('should load config from test environment', () => { expect(service.get('NODE_ENV')).toBe('test'); }); });

Testing Controllers

Mocking services and testing request/response handling.

import { Test, TestingModule } from '@nestjs/testing'; import { UserController } from './user.controller'; import { UserService } from './user.service'; import { CreateUserDto } from './dto/create-user.dto'; import { NotFoundException } from '@nestjs/common';

describe('UserController', () => { let controller: UserController; let service: UserService;

const mockUserService = { findAll: jest.fn(), findOne: jest.fn(), create: jest.fn(), update: jest.fn(), remove: jest.fn(), };

beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [UserController], providers: [ { provide: UserService, useValue: mockUserService, }, ], }).compile();

controller = module.get<UserController>(UserController);
service = module.get<UserService>(UserService);

});

afterEach(() => { jest.clearAllMocks(); });

describe('findAll', () => { it('should return an array of users', async () => { const users = [ { id: 1, name: 'John', email: 'john@example.com' }, { id: 2, name: 'Jane', email: 'jane@example.com' }, ];

  mockUserService.findAll.mockResolvedValue(users);

  const result = await controller.findAll();

  expect(result).toEqual(users);
  expect(service.findAll).toHaveBeenCalledTimes(1);
});

it('should return empty array when no users', async () => {
  mockUserService.findAll.mockResolvedValue([]);

  const result = await controller.findAll();

  expect(result).toEqual([]);
});

});

describe('findOne', () => { it('should return a user by id', async () => { const user = { id: 1, name: 'John', email: 'john@example.com' }; mockUserService.findOne.mockResolvedValue(user);

  const result = await controller.findOne('1');

  expect(result).toEqual(user);
  expect(service.findOne).toHaveBeenCalledWith(1);
});

it('should throw NotFoundException when user not found', async () => {
  mockUserService.findOne.mockRejectedValue(
    new NotFoundException('User not found'),
  );

  await expect(controller.findOne('999')).rejects.toThrow(
    NotFoundException,
  );
});

});

describe('create', () => { it('should create a new user', async () => { const createUserDto: CreateUserDto = { name: 'John', email: 'john@example.com', password: 'password123', };

  const createdUser = { id: 1, ...createUserDto };
  mockUserService.create.mockResolvedValue(createdUser);

  const result = await controller.create(createUserDto);

  expect(result).toEqual(createdUser);
  expect(service.create).toHaveBeenCalledWith(createUserDto);
});

});

describe('update', () => { it('should update a user', async () => { const updateDto = { name: 'Updated Name' }; const updatedUser = { id: 1, name: 'Updated Name', email: 'john@example.com' };

  mockUserService.update.mockResolvedValue(updatedUser);

  const result = await controller.update('1', updateDto);

  expect(result).toEqual(updatedUser);
  expect(service.update).toHaveBeenCalledWith(1, updateDto);
});

});

describe('remove', () => { it('should delete a user', async () => { mockUserService.remove.mockResolvedValue({ deleted: true });

  const result = await controller.remove('1');

  expect(result).toEqual({ deleted: true });
  expect(service.remove).toHaveBeenCalledWith(1);
});

}); });

Testing Services

Mocking repositories and database operations.

import { Test, TestingModule } from '@nestjs/testing'; import { UserService } from './user.service'; import { Repository } from 'typeorm'; import { User } from './entities/user.entity'; import { getRepositoryToken } from '@nestjs/typeorm'; import { NotFoundException, ConflictException } from '@nestjs/common';

describe('UserService', () => { let service: UserService; let repository: Repository<User>;

const mockRepository = { find: jest.fn(), findOne: jest.fn(), findOneBy: jest.fn(), save: jest.fn(), create: jest.fn(), delete: jest.fn(), update: jest.fn(), };

beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ UserService, { provide: getRepositoryToken(User), useValue: mockRepository, }, ], }).compile();

service = module.get&#x3C;UserService>(UserService);
repository = module.get&#x3C;Repository&#x3C;User>>(getRepositoryToken(User));

});

describe('findAll', () => { it('should return an array of users', async () => { const users = [{ id: 1, name: 'John', email: 'john@example.com' }]; mockRepository.find.mockResolvedValue(users);

  const result = await service.findAll();

  expect(result).toEqual(users);
  expect(repository.find).toHaveBeenCalled();
});

});

describe('findOne', () => { it('should return a user', async () => { const user = { id: 1, name: 'John', email: 'john@example.com' }; mockRepository.findOneBy.mockResolvedValue(user);

  const result = await service.findOne(1);

  expect(result).toEqual(user);
  expect(repository.findOneBy).toHaveBeenCalledWith({ id: 1 });
});

it('should throw NotFoundException when user not found', async () => {
  mockRepository.findOneBy.mockResolvedValue(null);

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

});

describe('create', () => { it('should create a new user', async () => { const createDto = { name: 'John', email: 'john@example.com', password: 'password123', }; const user = { id: 1, ...createDto };

  mockRepository.findOneBy.mockResolvedValue(null); // Email not taken
  mockRepository.create.mockReturnValue(user);
  mockRepository.save.mockResolvedValue(user);

  const result = await service.create(createDto);

  expect(result).toEqual(user);
  expect(repository.create).toHaveBeenCalledWith(createDto);
  expect(repository.save).toHaveBeenCalledWith(user);
});

it('should throw ConflictException when email exists', async () => {
  const createDto = {
    name: 'John',
    email: 'john@example.com',
    password: 'password123',
  };

  mockRepository.findOneBy.mockResolvedValue({ id: 1 }); // Email exists

  await expect(service.create(createDto)).rejects.toThrow(
    ConflictException,
  );
});

});

describe('update', () => { it('should update a user', async () => { const updateDto = { name: 'Updated Name' }; const existingUser = { id: 1, name: 'John', email: 'john@example.com' }; const updatedUser = { ...existingUser, ...updateDto };

  mockRepository.findOneBy.mockResolvedValue(existingUser);
  mockRepository.save.mockResolvedValue(updatedUser);

  const result = await service.update(1, updateDto);

  expect(result).toEqual(updatedUser);
  expect(repository.save).toHaveBeenCalled();
});

});

describe('remove', () => { it('should delete a user', async () => { const user = { id: 1, name: 'John', email: 'john@example.com' }; mockRepository.findOneBy.mockResolvedValue(user); mockRepository.delete.mockResolvedValue({ affected: 1 });

  await service.remove(1);

  expect(repository.delete).toHaveBeenCalledWith(1);
});

it('should throw NotFoundException when deleting non-existent user', async () => {
  mockRepository.findOneBy.mockResolvedValue(null);

  await expect(service.remove(999)).rejects.toThrow(NotFoundException);
});

}); });

Testing Providers

Factory providers and async providers.

import { Test, TestingModule } from '@nestjs/testing'; import { ConfigService } from '@nestjs/config'; import { DatabaseService } from './database.service';

describe('Factory Providers', () => { let databaseService: DatabaseService;

beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ { provide: 'DATABASE_CONNECTION', useFactory: (config: ConfigService) => { return { host: config.get('DB_HOST'), port: config.get('DB_PORT'), database: config.get('DB_NAME'), }; }, inject: [ConfigService], }, { provide: ConfigService, useValue: { get: jest.fn((key: string) => { const config = { DB_HOST: 'localhost', DB_PORT: 5432, DB_NAME: 'test_db', }; return config[key]; }), }, }, DatabaseService, ], }).compile();

databaseService = module.get&#x3C;DatabaseService>(DatabaseService);

});

it('should create database connection with correct config', () => { const connection = databaseService.getConnection(); expect(connection.host).toBe('localhost'); expect(connection.port).toBe(5432); expect(connection.database).toBe('test_db'); }); });

// Async provider testing describe('Async Providers', () => { let service: any;

beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ { provide: 'ASYNC_CONNECTION', useFactory: async () => { await new Promise((resolve) => setTimeout(resolve, 100)); return { connected: true }; }, }, ], }).compile();

service = module.get('ASYNC_CONNECTION');

});

it('should resolve async provider', () => { expect(service.connected).toBe(true); }); });

Testing Guards

Authentication and authorization guards.

import { Test, TestingModule } from '@nestjs/testing'; import { JwtAuthGuard } from './jwt-auth.guard'; import { JwtService } from '@nestjs/jwt'; import { ExecutionContext, UnauthorizedException } from '@nestjs/common';

describe('JwtAuthGuard', () => { let guard: JwtAuthGuard; let jwtService: JwtService;

const mockJwtService = { verifyAsync: jest.fn(), };

beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ JwtAuthGuard, { provide: JwtService, useValue: mockJwtService, }, ], }).compile();

guard = module.get&#x3C;JwtAuthGuard>(JwtAuthGuard);
jwtService = module.get&#x3C;JwtService>(JwtService);

});

it('should allow request with valid token', async () => { const mockContext = createMockExecutionContext({ headers: { authorization: 'Bearer valid-token' }, });

mockJwtService.verifyAsync.mockResolvedValue({
  userId: 1,
  email: 'user@example.com',
});

const result = await guard.canActivate(mockContext);

expect(result).toBe(true);
expect(jwtService.verifyAsync).toHaveBeenCalledWith('valid-token', {
  secret: expect.any(String),
});

});

it('should deny request without token', async () => { const mockContext = createMockExecutionContext({ headers: {}, });

await expect(guard.canActivate(mockContext)).rejects.toThrow(
  UnauthorizedException,
);

});

it('should deny request with invalid token', async () => { const mockContext = createMockExecutionContext({ headers: { authorization: 'Bearer invalid-token' }, });

mockJwtService.verifyAsync.mockRejectedValue(new Error('Invalid token'));

await expect(guard.canActivate(mockContext)).rejects.toThrow(
  UnauthorizedException,
);

}); });

// Helper function function createMockExecutionContext(request: any): ExecutionContext { return { switchToHttp: () => ({ getRequest: () => request, getResponse: () => ({}), }), getHandler: () => ({}), getClass: () => ({}), } as ExecutionContext; }

// Testing RolesGuard import { RolesGuard } from './roles.guard'; import { Reflector } from '@nestjs/core'; import { ForbiddenException } from '@nestjs/common';

describe('RolesGuard', () => { let guard: RolesGuard; let reflector: Reflector;

beforeEach(() => { reflector = new Reflector(); guard = new RolesGuard(reflector); });

it('should allow access when user has required role', () => { jest.spyOn(reflector, 'getAllAndOverride').mockReturnValue(['admin']);

const mockContext = createMockExecutionContext({
  user: { id: 1, roles: ['admin'] },
});

const result = guard.canActivate(mockContext);

expect(result).toBe(true);

});

it('should deny access when user lacks required role', () => { jest.spyOn(reflector, 'getAllAndOverride').mockReturnValue(['admin']);

const mockContext = createMockExecutionContext({
  user: { id: 1, roles: ['user'] },
});

expect(() => guard.canActivate(mockContext)).toThrow(ForbiddenException);

});

it('should allow access when no roles required', () => { jest.spyOn(reflector, 'getAllAndOverride').mockReturnValue(undefined);

const mockContext = createMockExecutionContext({
  user: { id: 1, roles: [] },
});

const result = guard.canActivate(mockContext);

expect(result).toBe(true);

}); });

Testing Interceptors

Transformation and logging interceptors.

import { Test, TestingModule } from '@nestjs/testing'; import { TransformInterceptor } from './transform.interceptor'; import { ExecutionContext, CallHandler } from '@nestjs/common'; import { of } from 'rxjs';

describe('TransformInterceptor', () => { let interceptor: TransformInterceptor;

beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [TransformInterceptor], }).compile();

interceptor = module.get&#x3C;TransformInterceptor>(TransformInterceptor);

});

it('should transform response data', (done) => { const mockContext = { switchToHttp: () => ({ getRequest: () => ({ url: '/test' }), }), } as ExecutionContext;

const mockCallHandler: CallHandler = {
  handle: () => of({ name: 'Test', value: 123 }),
};

interceptor.intercept(mockContext, mockCallHandler).subscribe({
  next: (result) => {
    expect(result).toHaveProperty('data');
    expect(result.data).toEqual({ name: 'Test', value: 123 });
    expect(result).toHaveProperty('timestamp');
    expect(result).toHaveProperty('path');
    expect(result.path).toBe('/test');
    done();
  },
});

}); });

// Testing caching interceptor import { CacheInterceptor } from './cache.interceptor'; import { CACHE_MANAGER } from '@nestjs/cache-manager';

describe('CacheInterceptor', () => { let interceptor: CacheInterceptor; let cacheManager: any;

const mockCacheManager = { get: jest.fn(), set: jest.fn(), };

beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ CacheInterceptor, { provide: CACHE_MANAGER, useValue: mockCacheManager, }, ], }).compile();

interceptor = module.get&#x3C;CacheInterceptor>(CacheInterceptor);
cacheManager = module.get(CACHE_MANAGER);

});

it('should return cached data if available', async (done) => { const cachedData = { cached: true }; mockCacheManager.get.mockResolvedValue(cachedData);

const mockContext = {
  switchToHttp: () => ({
    getRequest: () => ({ method: 'GET', url: '/test' }),
  }),
} as ExecutionContext;

const mockCallHandler: CallHandler = {
  handle: () => of({ fresh: true }),
};

const result$ = await interceptor.intercept(mockContext, mockCallHandler);

result$.subscribe({
  next: (result) => {
    expect(result).toEqual(cachedData);
    expect(cacheManager.get).toHaveBeenCalledWith('GET:/test');
    done();
  },
});

});

it('should cache fresh data', (done) => { const freshData = { fresh: true }; mockCacheManager.get.mockResolvedValue(null);

const mockContext = {
  switchToHttp: () => ({
    getRequest: () => ({ method: 'GET', url: '/test' }),
  }),
} as ExecutionContext;

const mockCallHandler: CallHandler = {
  handle: () => of(freshData),
};

interceptor.intercept(mockContext, mockCallHandler).then((result$) => {
  result$.subscribe({
    next: async (result) => {
      expect(result).toEqual(freshData);
      // Give time for cache to be set
      await new Promise((resolve) => setTimeout(resolve, 100));
      expect(cacheManager.set).toHaveBeenCalled();
      done();
    },
  });
});

}); });

Testing Pipes

Validation and transformation pipes.

import { Test, TestingModule } from '@nestjs/testing'; import { ValidationPipe, BadRequestException } from '@nestjs/common'; import { ParseIntPipe } from '@nestjs/common'; import { ArgumentMetadata } from '@nestjs/common';

describe('ParseIntPipe', () => { let pipe: ParseIntPipe;

beforeEach(() => { pipe = new ParseIntPipe(); });

it('should parse valid number string', async () => { const metadata: ArgumentMetadata = { type: 'param', metatype: Number, data: 'id', };

const result = await pipe.transform('123', metadata);

expect(result).toBe(123);

});

it('should throw error for invalid number string', async () => { const metadata: ArgumentMetadata = { type: 'param', metatype: Number, data: 'id', };

await expect(pipe.transform('abc', metadata)).rejects.toThrow(
  BadRequestException,
);

}); });

// Custom validation pipe testing import { CustomValidationPipe } from './custom-validation.pipe'; import { IsString, IsEmail, MinLength } from 'class-validator';

class CreateUserDto { @IsString() @MinLength(3) name: string;

@IsEmail() email: string; }

describe('CustomValidationPipe', () => { let pipe: CustomValidationPipe;

beforeEach(() => { pipe = new CustomValidationPipe(); });

it('should validate valid DTO', async () => { const dto = { name: 'John Doe', email: 'john@example.com', };

const metadata: ArgumentMetadata = {
  type: 'body',
  metatype: CreateUserDto,
};

const result = await pipe.transform(dto, metadata);

expect(result).toEqual(dto);

});

it('should throw error for invalid DTO', async () => { const dto = { name: 'Jo', // Too short email: 'invalid-email', };

const metadata: ArgumentMetadata = {
  type: 'body',
  metatype: CreateUserDto,
};

await expect(pipe.transform(dto, metadata)).rejects.toThrow(
  BadRequestException,
);

}); });

Integration Testing / E2E Tests

Testing with supertest and real HTTP requests.

import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication, ValidationPipe } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; import { getRepositoryToken } from '@nestjs/typeorm'; import { User } from '../src/users/entities/user.entity';

describe('UserController (e2e)', () => { let app: INestApplication; let userRepository: any;

beforeAll(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile();

app = moduleFixture.createNestApplication();
app.useGlobalPipes(new ValidationPipe());

userRepository = moduleFixture.get(getRepositoryToken(User));

await app.init();

});

afterAll(async () => { await app.close(); });

beforeEach(async () => { // Clean database before each test await userRepository.query('DELETE FROM users'); });

describe('/users (POST)', () => { it('should create a new user', () => { return request(app.getHttpServer()) .post('/users') .send({ name: 'John Doe', email: 'john@example.com', password: 'password123', }) .expect(201) .expect((res) => { expect(res.body).toHaveProperty('id'); expect(res.body.name).toBe('John Doe'); expect(res.body.email).toBe('john@example.com'); expect(res.body).not.toHaveProperty('password'); }); });

it('should return 400 for invalid data', () => {
  return request(app.getHttpServer())
    .post('/users')
    .send({
      name: 'Jo', // Too short
      email: 'invalid-email',
    })
    .expect(400);
});

});

describe('/users (GET)', () => { it('should return all users', async () => { // Seed data await userRepository.save([ { name: 'User 1', email: 'user1@example.com' }, { name: 'User 2', email: 'user2@example.com' }, ]);

  return request(app.getHttpServer())
    .get('/users')
    .expect(200)
    .expect((res) => {
      expect(res.body).toHaveLength(2);
      expect(res.body[0]).toHaveProperty('id');
      expect(res.body[0]).toHaveProperty('name');
    });
});

it('should return empty array when no users', () => {
  return request(app.getHttpServer())
    .get('/users')
    .expect(200)
    .expect([]);
});

});

describe('/users/:id (GET)', () => { it('should return a user by id', async () => { const user = await userRepository.save({ name: 'John Doe', email: 'john@example.com', });

  return request(app.getHttpServer())
    .get(`/users/${user.id}`)
    .expect(200)
    .expect((res) => {
      expect(res.body.id).toBe(user.id);
      expect(res.body.name).toBe('John Doe');
    });
});

it('should return 404 for non-existent user', () => {
  return request(app.getHttpServer()).get('/users/999').expect(404);
});

});

describe('/users/:id (PATCH)', () => { it('should update a user', async () => { const user = await userRepository.save({ name: 'John Doe', email: 'john@example.com', });

  return request(app.getHttpServer())
    .patch(`/users/${user.id}`)
    .send({ name: 'Jane Doe' })
    .expect(200)
    .expect((res) => {
      expect(res.body.name).toBe('Jane Doe');
    });
});

});

describe('/users/:id (DELETE)', () => { it('should delete a user', async () => { const user = await userRepository.save({ name: 'John Doe', email: 'john@example.com', });

  await request(app.getHttpServer())
    .delete(`/users/${user.id}`)
    .expect(200);

  // Verify deletion
  const deletedUser = await userRepository.findOne({ where: { id: user.id } });
  expect(deletedUser).toBeNull();
});

}); });

Testing with Database

In-memory, Docker, and test containers.

// In-memory SQLite for testing import { Test, TestingModule } from '@nestjs/testing'; import { TypeOrmModule } from '@nestjs/typeorm'; import { User } from './entities/user.entity';

describe('UserService with In-Memory DB', () => { let module: TestingModule; let service: UserService;

beforeAll(async () => { module = await Test.createTestingModule({ imports: [ TypeOrmModule.forRoot({ type: 'sqlite', database: ':memory:', entities: [User], synchronize: true, dropSchema: true, }), TypeOrmModule.forFeature([User]), ], providers: [UserService], }).compile();

service = module.get&#x3C;UserService>(UserService);

});

afterAll(async () => { await module.close(); });

it('should create and retrieve a user', async () => { const user = await service.create({ name: 'John', email: 'john@example.com', password: 'password123', });

expect(user.id).toBeDefined();

const foundUser = await service.findOne(user.id);
expect(foundUser.name).toBe('John');

}); });

// Test with Docker container (using testcontainers) import { GenericContainer, StartedTestContainer } from 'testcontainers';

describe('UserService with PostgreSQL Container', () => { let container: StartedTestContainer; let module: TestingModule;

beforeAll(async () => { // Start PostgreSQL container container = await new GenericContainer('postgres:15') .withEnvironment({ POSTGRES_USER: 'test', POSTGRES_PASSWORD: 'test', POSTGRES_DB: 'testdb', }) .withExposedPorts(5432) .start();

const port = container.getMappedPort(5432);

module = await Test.createTestingModule({
  imports: [
    TypeOrmModule.forRoot({
      type: 'postgres',
      host: 'localhost',
      port,
      username: 'test',
      password: 'test',
      database: 'testdb',
      entities: [User],
      synchronize: true,
    }),
    TypeOrmModule.forFeature([User]),
  ],
  providers: [UserService],
}).compile();

}, 60000);

afterAll(async () => { await module.close(); await container.stop(); });

it('should work with real PostgreSQL', async () => { const service = module.get<UserService>(UserService); const user = await service.create({ name: 'John', email: 'john@example.com', password: 'password123', });

expect(user.id).toBeDefined();

}); });

Testing WebSockets

WebSocket gateway testing.

import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import { io, Socket } from 'socket.io-client'; import { ChatGateway } from './chat.gateway';

describe('ChatGateway (e2e)', () => { let app: INestApplication; let clientSocket: Socket;

beforeAll(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ providers: [ChatGateway], }).compile();

app = moduleFixture.createNestApplication();
await app.listen(3001);

});

afterAll(async () => { await app.close(); });

beforeEach((done) => { clientSocket = io('http://localhost:3001'); clientSocket.on('connect', done); });

afterEach(() => { clientSocket.close(); });

it('should receive messages', (done) => { clientSocket.emit('message', { text: 'Hello World' });

clientSocket.on('message', (data) => {
  expect(data.text).toBe('Hello World');
  done();
});

});

it('should handle multiple clients', (done) => { const client2 = io('http://localhost:3001');

client2.on('connect', () => {
  clientSocket.emit('message', { text: 'Broadcast' });

  client2.on('message', (data) => {
    expect(data.text).toBe('Broadcast');
    client2.close();
    done();
  });
});

}); });

Testing GraphQL Resolvers

GraphQL testing with supertest.

import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { GraphQLModule } from '@nestjs/graphql'; import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';

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

beforeAll(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [ GraphQLModule.forRoot<ApolloDriverConfig>({ driver: ApolloDriver, autoSchemaFile: true, }), UserModule, ], }).compile();

app = moduleFixture.createNestApplication();
await app.init();

});

afterAll(async () => { await app.close(); });

it('should query users', () => { return request(app.getHttpServer()) .post('/graphql') .send({ query: query { users { id name email } } , }) .expect(200) .expect((res) => { expect(res.body.data.users).toBeDefined(); expect(Array.isArray(res.body.data.users)).toBe(true); }); });

it('should create a user', () => { return request(app.getHttpServer()) .post('/graphql') .send({ query: mutation { createUser(createUserInput: { name: "John Doe" email: "john@example.com" }) { id name email } } , }) .expect(200) .expect((res) => { expect(res.body.data.createUser).toHaveProperty('id'); expect(res.body.data.createUser.name).toBe('John Doe'); }); }); });

Mocking Strategies

jest.mock and custom providers.

// Mock entire module jest.mock('./user.service'); import { UserService } from './user.service';

describe('UserController with mocked service', () => { let controller: UserController;

beforeEach(() => { controller = new UserController(new UserService()); });

it('should use mocked service', async () => { jest.spyOn(UserService.prototype, 'findAll').mockResolvedValue([]); const result = await controller.findAll(); expect(result).toEqual([]); }); });

// Partial mocking const mockUserService = { findAll: jest.fn(), findOne: jest.fn(), } as unknown as UserService;

// Mock external dependencies import axios from 'axios'; jest.mock('axios'); const mockedAxios = axios as jest.Mocked<typeof axios>;

describe('ExternalApiService', () => { it('should fetch data from external API', async () => { mockedAxios.get.mockResolvedValue({ data: { result: 'success' } });

const service = new ExternalApiService();
const result = await service.fetchData();

expect(result).toEqual({ result: 'success' });
expect(mockedAxios.get).toHaveBeenCalledWith('https://api.example.com/data');

}); });

// Custom mock factory function createMockRepository() { return { find: jest.fn(), findOne: jest.fn(), save: jest.fn(), create: jest.fn((dto) => dto), delete: jest.fn(), }; }

Test Fixtures and Factories

Creating reusable test data.

// User factory export class UserFactory { static create(overrides?: Partial<User>): User { return { id: 1, name: 'John Doe', email: 'john@example.com', password: 'hashed_password', createdAt: new Date(), updatedAt: new Date(), ...overrides, }; }

static createMany(count: number, overrides?: Partial<User>): User[] { return Array.from({ length: count }, (_, i) => this.create({ id: i + 1, ...overrides }), ); } }

// Usage in tests describe('UserService', () => { it('should find users', async () => { const users = UserFactory.createMany(3); mockRepository.find.mockResolvedValue(users);

const result = await service.findAll();
expect(result).toHaveLength(3);

}); });

// Builder pattern for complex entities class UserBuilder { private user: Partial<User> = {};

withName(name: string): this { this.user.name = name; return this; }

withEmail(email: string): this { this.user.email = email; return this; }

asAdmin(): this { this.user.role = 'admin'; return this; }

build(): User { return { id: 1, name: 'John Doe', email: 'john@example.com', password: 'password', role: 'user', createdAt: new Date(), updatedAt: new Date(), ...this.user, } as User; } }

// Usage const adminUser = new UserBuilder() .withName('Admin User') .withEmail('admin@example.com') .asAdmin() .build();

Code Coverage and CI/CD

Testing configuration for coverage and automation.

// jest.config.js module.exports = { moduleFileExtensions: ['js', 'json', 'ts'], rootDir: 'src', testRegex: '.\.spec\.ts$', transform: { '^.+\.(t|j)s$': 'ts-jest', }, collectCoverageFrom: [ '**/.(t|j)s', '!/*.module.ts', '!/node_modules/', '!/dist/**', ], coverageDirectory: '../coverage', testEnvironment: 'node', coverageThreshold: { global: { branches: 80, functions: 80, lines: 80, statements: 80, }, }, };

// package.json scripts { "scripts": { "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json" } }

// GitHub Actions CI // .github/workflows/test.yml name: Tests user-invocable: false

on: [push, pull_request]

jobs: test: runs-on: ubuntu-latest

steps:
  - uses: actions/checkout@v3
  - uses: actions/setup-node@v3
    with:
      node-version: '18'

  - run: npm ci
  - run: npm run test:cov

  - name: Upload coverage
    uses: codecov/codecov-action@v3
    with:
      files: ./coverage/lcov.info

When to Use This Skill

Use nestjs-testing when:

  • Building production applications that require reliability

  • Implementing new features that need verification

  • Refactoring code safely with confidence

  • Debugging complex issues through isolated tests

  • Ensuring API contracts are maintained

  • Validating business logic correctness

  • Setting up CI/CD pipelines

  • Documenting expected behavior through tests

  • Preventing regressions in existing functionality

  • Meeting code quality standards and coverage requirements

NestJS Testing Best Practices

  • Test isolation - Each test should be independent and not rely on others

  • AAA pattern - Structure tests as Arrange, Act, Assert

  • Mock external dependencies - Mock databases, APIs, and third-party services

  • Use factories - Create test data with factories for consistency

  • Test behavior, not implementation - Focus on what the code does, not how

  • Meaningful test names - Describe what is being tested and expected outcome

  • Setup and teardown - Clean up resources after tests

  • Coverage goals - Aim for 80%+ coverage but focus on critical paths

  • E2E for critical flows - Test important user journeys end-to-end

  • Run tests in CI/CD - Automate testing in your deployment pipeline

NestJS Testing Common Pitfalls

  • Testing implementation details - Tests break when refactoring

  • Shared state - Tests fail when run in different orders

  • Not cleaning up - Database pollution between tests

  • Over-mocking - Mocking everything reduces test value

  • Flaky tests - Tests that randomly fail due to timing issues

  • Slow tests - Not using in-memory databases for unit tests

  • Missing edge cases - Only testing happy paths

  • Incomplete mocks - Missing methods on mocked services

  • Not testing errors - Only testing successful scenarios

  • Poor test organization - Hard to find and maintain tests

Resources

  • NestJS Testing Documentation

  • Jest Documentation

  • Supertest Documentation

  • TestContainers Node

  • Testing TypeORM

  • Socket.IO Client Testing

  • Apollo Testing Utilities

  • Code Coverage with Jest

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

android-jetpack-compose

No summary provided by upstream source.

Repository SourceNeeds Review
General

fastapi-async-patterns

No summary provided by upstream source.

Repository SourceNeeds Review
General

storybook-story-writing

No summary provided by upstream source.

Repository SourceNeeds Review
General

atomic-design-fundamentals

No summary provided by upstream source.

Repository SourceNeeds Review