260223:1415 20260223 nextJS & nestJS Best pratices
All checks were successful
Build and Deploy / deploy (push) Successful in 4m44s
All checks were successful
Build and Deploy / deploy (push) Successful in 4m44s
This commit is contained in:
@@ -0,0 +1,153 @@
|
||||
---
|
||||
title: Use Testing Module for Unit Tests
|
||||
impact: HIGH
|
||||
impactDescription: Enables proper isolated testing with mocked dependencies
|
||||
tags: testing, unit-tests, mocking, jest
|
||||
---
|
||||
|
||||
## Use Testing Module for Unit Tests
|
||||
|
||||
Use `@nestjs/testing` module to create isolated test environments with mocked dependencies. This ensures your tests run fast, don't depend on external services, and properly test your business logic in isolation.
|
||||
|
||||
**Incorrect (manual instantiation bypassing DI):**
|
||||
|
||||
```typescript
|
||||
// Instantiate services manually without DI
|
||||
describe('UsersService', () => {
|
||||
it('should create user', async () => {
|
||||
// Manual instantiation bypasses DI
|
||||
const repo = new UserRepository(); // Real repo!
|
||||
const service = new UsersService(repo);
|
||||
|
||||
const user = await service.create({ name: 'Test' });
|
||||
// This hits the real database!
|
||||
});
|
||||
});
|
||||
|
||||
// Test implementation details
|
||||
describe('UsersController', () => {
|
||||
it('should call service', async () => {
|
||||
const service = { create: jest.fn() };
|
||||
const controller = new UsersController(service as any);
|
||||
|
||||
await controller.create({ name: 'Test' });
|
||||
|
||||
expect(service.create).toHaveBeenCalled(); // Tests implementation, not behavior
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
**Correct (use Test.createTestingModule with mocked dependencies):**
|
||||
|
||||
```typescript
|
||||
// Use Test.createTestingModule for proper DI
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
|
||||
describe('UsersService', () => {
|
||||
let service: UsersService;
|
||||
let repo: jest.Mocked<UserRepository>;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
UsersService,
|
||||
{
|
||||
provide: UserRepository,
|
||||
useValue: {
|
||||
save: jest.fn(),
|
||||
findOne: jest.fn(),
|
||||
find: jest.fn(),
|
||||
},
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
service = module.get<UsersService>(UsersService);
|
||||
repo = module.get(UserRepository);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('create', () => {
|
||||
it('should save and return user', async () => {
|
||||
const dto = { name: 'John', email: 'john@test.com' };
|
||||
const expectedUser = { id: '1', ...dto };
|
||||
|
||||
repo.save.mockResolvedValue(expectedUser);
|
||||
|
||||
const result = await service.create(dto);
|
||||
|
||||
expect(result).toEqual(expectedUser);
|
||||
expect(repo.save).toHaveBeenCalledWith(dto);
|
||||
});
|
||||
|
||||
it('should throw on duplicate email', async () => {
|
||||
repo.findOne.mockResolvedValue({ id: '1', email: 'test@test.com' });
|
||||
|
||||
await expect(
|
||||
service.create({ name: 'Test', email: 'test@test.com' }),
|
||||
).rejects.toThrow(ConflictException);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findById', () => {
|
||||
it('should return user when found', async () => {
|
||||
const user = { id: '1', name: 'John' };
|
||||
repo.findOne.mockResolvedValue(user);
|
||||
|
||||
const result = await service.findById('1');
|
||||
|
||||
expect(result).toEqual(user);
|
||||
});
|
||||
|
||||
it('should throw NotFoundException when not found', async () => {
|
||||
repo.findOne.mockResolvedValue(null);
|
||||
|
||||
await expect(service.findById('999')).rejects.toThrow(NotFoundException);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Testing guards and interceptors
|
||||
describe('RolesGuard', () => {
|
||||
let guard: RolesGuard;
|
||||
let reflector: Reflector;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module = await Test.createTestingModule({
|
||||
providers: [RolesGuard, Reflector],
|
||||
}).compile();
|
||||
|
||||
guard = module.get<RolesGuard>(RolesGuard);
|
||||
reflector = module.get<Reflector>(Reflector);
|
||||
});
|
||||
|
||||
it('should allow when no roles required', () => {
|
||||
const context = createMockExecutionContext({ user: { roles: [] } });
|
||||
jest.spyOn(reflector, 'getAllAndOverride').mockReturnValue(undefined);
|
||||
|
||||
expect(guard.canActivate(context)).toBe(true);
|
||||
});
|
||||
|
||||
it('should allow admin for admin-only route', () => {
|
||||
const context = createMockExecutionContext({ user: { roles: ['admin'] } });
|
||||
jest.spyOn(reflector, 'getAllAndOverride').mockReturnValue(['admin']);
|
||||
|
||||
expect(guard.canActivate(context)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
function createMockExecutionContext(request: Partial<Request>): ExecutionContext {
|
||||
return {
|
||||
switchToHttp: () => ({
|
||||
getRequest: () => request,
|
||||
}),
|
||||
getHandler: () => jest.fn(),
|
||||
getClass: () => jest.fn(),
|
||||
} as ExecutionContext;
|
||||
}
|
||||
```
|
||||
|
||||
Reference: [NestJS Testing](https://docs.nestjs.com/fundamentals/testing)
|
||||
Reference in New Issue
Block a user