managarten/docs/test-examples/backend/example.controller.spec.ts
2025-11-27 17:26:18 +01:00

251 lines
7.2 KiB
TypeScript

/**
* Example NestJS Controller Test
*
* This demonstrates best practices for testing NestJS controllers:
* - Mock all dependencies
* - Test successful responses
* - Test error handling
* - Test authentication/authorization
* - Test validation
*/
import { Test, TestingModule } from '@nestjs/testing';
import { BadRequestException, UnauthorizedException, NotFoundException } from '@nestjs/common';
import { ExampleController } from '../example.controller';
import { ExampleService } from '../example.service';
import { CreateExampleDto } from '../dto/create-example.dto';
import { UpdateExampleDto } from '../dto/update-example.dto';
describe('ExampleController', () => {
let controller: ExampleController;
let service: ExampleService;
// Mock data
const mockUser = { sub: 'user-123', email: 'test@example.com' };
const mockExample = {
id: 'example-123',
title: 'Test Example',
description: 'Test description',
userId: 'user-123',
createdAt: new Date(),
updatedAt: new Date(),
};
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [ExampleController],
providers: [
{
provide: ExampleService,
useValue: {
create: jest.fn(),
findAll: jest.fn(),
findOne: jest.fn(),
update: jest.fn(),
remove: jest.fn(),
},
},
],
}).compile();
controller = module.get<ExampleController>(ExampleController);
service = module.get<ExampleService>(ExampleService);
});
afterEach(() => {
jest.clearAllMocks();
});
describe('create', () => {
const createDto: CreateExampleDto = {
title: 'New Example',
description: 'New description',
};
it('should create an example successfully', async () => {
const expectedResult = {
data: { ...mockExample, ...createDto },
error: null,
};
jest.spyOn(service, 'create').mockResolvedValue(expectedResult);
const result = await controller.create(createDto, { user: mockUser });
expect(result).toEqual(expectedResult.data);
expect(service.create).toHaveBeenCalledWith(createDto, mockUser.sub);
expect(service.create).toHaveBeenCalledTimes(1);
});
it('should throw BadRequestException for invalid data', async () => {
const invalidDto = { title: '', description: 'Test' } as CreateExampleDto;
jest.spyOn(service, 'create').mockResolvedValue({
data: null,
error: new Error('Validation failed'),
});
await expect(controller.create(invalidDto, { user: mockUser })).rejects.toThrow(BadRequestException);
});
it('should throw UnauthorizedException when user is not authenticated', async () => {
await expect(controller.create(createDto, { user: null })).rejects.toThrow(UnauthorizedException);
});
it('should handle service errors gracefully', async () => {
jest.spyOn(service, 'create').mockResolvedValue({
data: null,
error: new Error('Database error'),
});
await expect(controller.create(createDto, { user: mockUser })).rejects.toThrow();
});
});
describe('findAll', () => {
it('should return all examples for the user', async () => {
const expectedResult = {
data: [mockExample],
error: null,
};
jest.spyOn(service, 'findAll').mockResolvedValue(expectedResult);
const result = await controller.findAll({ user: mockUser });
expect(result).toEqual(expectedResult.data);
expect(service.findAll).toHaveBeenCalledWith(mockUser.sub);
expect(service.findAll).toHaveBeenCalledTimes(1);
});
it('should return empty array when user has no examples', async () => {
jest.spyOn(service, 'findAll').mockResolvedValue({
data: [],
error: null,
});
const result = await controller.findAll({ user: mockUser });
expect(result).toEqual([]);
});
it('should require authentication', async () => {
await expect(controller.findAll({ user: null })).rejects.toThrow(UnauthorizedException);
});
});
describe('findOne', () => {
const exampleId = 'example-123';
it('should return a single example', async () => {
jest.spyOn(service, 'findOne').mockResolvedValue({
data: mockExample,
error: null,
});
const result = await controller.findOne(exampleId, { user: mockUser });
expect(result).toEqual(mockExample);
expect(service.findOne).toHaveBeenCalledWith(exampleId, mockUser.sub);
});
it('should throw NotFoundException when example does not exist', async () => {
jest.spyOn(service, 'findOne').mockResolvedValue({
data: null,
error: new Error('Not found'),
});
await expect(controller.findOne('invalid-id', { user: mockUser })).rejects.toThrow(NotFoundException);
});
it('should not allow access to other users examples', async () => {
const otherUserExample = { ...mockExample, userId: 'other-user' };
jest.spyOn(service, 'findOne').mockResolvedValue({
data: otherUserExample,
error: null,
});
await expect(controller.findOne(exampleId, { user: mockUser })).rejects.toThrow(UnauthorizedException);
});
});
describe('update', () => {
const exampleId = 'example-123';
const updateDto: UpdateExampleDto = {
title: 'Updated Title',
};
it('should update an example successfully', async () => {
const updatedExample = { ...mockExample, ...updateDto };
jest.spyOn(service, 'update').mockResolvedValue({
data: updatedExample,
error: null,
});
const result = await controller.update(exampleId, updateDto, { user: mockUser });
expect(result).toEqual(updatedExample);
expect(service.update).toHaveBeenCalledWith(exampleId, updateDto, mockUser.sub);
});
it('should throw NotFoundException when example does not exist', async () => {
jest.spyOn(service, 'update').mockResolvedValue({
data: null,
error: new Error('Not found'),
});
await expect(controller.update('invalid-id', updateDto, { user: mockUser })).rejects.toThrow(
NotFoundException
);
});
it('should validate update data', async () => {
const invalidDto = { title: '' } as UpdateExampleDto;
jest.spyOn(service, 'update').mockResolvedValue({
data: null,
error: new Error('Validation failed'),
});
await expect(controller.update(exampleId, invalidDto, { user: mockUser })).rejects.toThrow(
BadRequestException
);
});
});
describe('remove', () => {
const exampleId = 'example-123';
it('should delete an example successfully', async () => {
jest.spyOn(service, 'remove').mockResolvedValue({
data: { success: true },
error: null,
});
const result = await controller.remove(exampleId, { user: mockUser });
expect(result).toEqual({ success: true });
expect(service.remove).toHaveBeenCalledWith(exampleId, mockUser.sub);
});
it('should throw NotFoundException when example does not exist', async () => {
jest.spyOn(service, 'remove').mockResolvedValue({
data: null,
error: new Error('Not found'),
});
await expect(controller.remove('invalid-id', { user: mockUser })).rejects.toThrow(NotFoundException);
});
it('should not allow deletion of other users examples', async () => {
jest.spyOn(service, 'remove').mockResolvedValue({
data: null,
error: new Error('Unauthorized'),
});
await expect(controller.remove(exampleId, { user: mockUser })).rejects.toThrow(UnauthorizedException);
});
});
});