T
Testing
Type-safe unit and integration testing in NestJS with Jest and proper mocking.
Code
typescript
1// user.service.spec.ts2import { Test, TestingModule } from "@nestjs/testing";3import { NotFoundException, ConflictException } from "@nestjs/common";4import { UserService } from "./user.service";5import { UserRepository } from "./user.repository";6import { CreateUserDto } from "./dto/create-user.dto";7 8// Mock types9type MockUserRepository = {10 [K in keyof UserRepository]: jest.Mock;11};12 13const createMockRepository = (): MockUserRepository => ({14 findAll: jest.fn(),15 findOne: jest.fn(),16 findByEmail: jest.fn(),17 create: jest.fn(),18 update: jest.fn(),19 delete: jest.fn(),20});21 22describe("UserService", () => {23 let service: UserService;24 let repository: MockUserRepository;25 26 const mockUser = {27 id: 1,28 name: "Test User",29 email: "test@example.com",30 role: "user" as const,31 createdAt: new Date(),32 updatedAt: new Date(),33 };34 35 beforeEach(async () => {36 const module: TestingModule = await Test.createTestingModule({37 providers: [38 UserService,39 {40 provide: UserRepository,41 useValue: createMockRepository(),42 },43 ],44 }).compile();45 46 service = module.get<UserService>(UserService);47 repository = module.get(UserRepository);48 });49 50 afterEach(() => {51 jest.clearAllMocks();52 });53 54 describe("findOne", () => {55 it("should return a user if found", async () => {56 repository.findOne.mockResolvedValue(mockUser);57 58 const result = await service.findOne(1);59 60 expect(result).toEqual(mockUser);61 expect(repository.findOne).toHaveBeenCalledWith(1);62 });63 64 it("should throw NotFoundException if user not found", async () => {65 repository.findOne.mockResolvedValue(null);66 67 await expect(service.findOne(999)).rejects.toThrow(NotFoundException);68 });69 });70 71 describe("create", () => {72 const createDto: CreateUserDto = {73 name: "New User",74 email: "new@example.com",75 password: "password123",76 };77 78 it("should create a new user", async () => {79 repository.findByEmail.mockResolvedValue(null);80 repository.create.mockResolvedValue({ id: 2, ...createDto, role: "user" });81 82 const result = await service.create(createDto);83 84 expect(result).toHaveProperty("id");85 expect(repository.create).toHaveBeenCalled();86 });87 88 it("should throw ConflictException if email exists", async () => {89 repository.findByEmail.mockResolvedValue(mockUser);90 91 await expect(service.create(createDto)).rejects.toThrow(ConflictException);92 });93 });94 95 describe("update", () => {96 it("should update an existing user", async () => {97 repository.findOne.mockResolvedValue(mockUser);98 repository.update.mockResolvedValue({ ...mockUser, name: "Updated" });99 100 const result = await service.update(1, { name: "Updated" });101 102 expect(result.name).toBe("Updated");103 });104 });105 106 describe("delete", () => {107 it("should delete an existing user", async () => {108 repository.findOne.mockResolvedValue(mockUser);109 repository.delete.mockResolvedValue(undefined);110 111 await expect(service.delete(1)).resolves.not.toThrow();112 });113 });114});115 116// user.controller.spec.ts117import { Test, TestingModule } from "@nestjs/testing";118import { UserController } from "./user.controller";119import { UserService } from "./user.service";120 121describe("UserController", () => {122 let controller: UserController;123 let service: jest.Mocked<UserService>;124 125 const mockUserService = {126 findAll: jest.fn(),127 findOne: jest.fn(),128 create: jest.fn(),129 update: jest.fn(),130 delete: jest.fn(),131 };132 133 beforeEach(async () => {134 const module: TestingModule = await Test.createTestingModule({135 controllers: [UserController],136 providers: [137 {138 provide: UserService,139 useValue: mockUserService,140 },141 ],142 }).compile();143 144 controller = module.get<UserController>(UserController);145 service = module.get(UserService);146 });147 148 describe("findAll", () => {149 it("should return array of users", async () => {150 const mockUsers = [{ id: 1, name: "User 1" }];151 service.findAll.mockResolvedValue({ data: mockUsers, total: 1 });152 153 const result = await controller.findAll({ page: 1, limit: 10 });154 155 expect(result.data).toEqual(mockUsers);156 });157 });158});159 160// app.e2e-spec.ts - Integration test161import { Test, TestingModule } from "@nestjs/testing";162import { INestApplication, ValidationPipe } from "@nestjs/common";163import * as request from "supertest";164import { AppModule } from "../src/app.module";165 166describe("UserController (e2e)", () => {167 let app: INestApplication;168 169 beforeAll(async () => {170 const moduleFixture: TestingModule = await Test.createTestingModule({171 imports: [AppModule],172 }).compile();173 174 app = moduleFixture.createNestApplication();175 app.useGlobalPipes(new ValidationPipe());176 await app.init();177 });178 179 afterAll(async () => {180 await app.close();181 });182 183 describe("/users (POST)", () => {184 it("should create a user", () => {185 return request(app.getHttpServer())186 .post("/users")187 .send({188 name: "Test User",189 email: "test@example.com",190 password: "password123",191 })192 .expect(201)193 .expect((res) => {194 expect(res.body).toHaveProperty("id");195 expect(res.body.email).toBe("test@example.com");196 });197 });198 199 it("should return 400 for invalid data", () => {200 return request(app.getHttpServer())201 .post("/users")202 .send({ name: "" })203 .expect(400);204 });205 });206 207 describe("/users (GET)", () => {208 it("should return users list", () => {209 return request(app.getHttpServer())210 .get("/users")211 .expect(200)212 .expect((res) => {213 expect(Array.isArray(res.body.data)).toBe(true);214 });215 });216 });217});Run this example locally
$ npm install --save-dev @nestjs/testing jest @types/jest supertest