/** * Example NestJS Service Test * * This demonstrates best practices for testing NestJS services: * - Mock database/external dependencies * - Test business logic thoroughly * - Test error handling * - Test edge cases * - Use Result pattern for error handling */ import { Test, TestingModule } from '@nestjs/testing'; import { ExampleService } from '../example.service'; import { SupabaseDataService } from '../../core/services/supabase-data.service'; import { ExternalApiService } from '../../core/services/external-api.service'; import { CreateExampleDto } from '../dto/create-example.dto'; describe('ExampleService', () => { let service: ExampleService; let supabaseService: jest.Mocked; let externalApiService: jest.Mocked; 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 () => { // Create mocked services const mockSupabaseService = { insertExample: jest.fn(), getExample: jest.fn(), getExamplesByUser: jest.fn(), updateExample: jest.fn(), deleteExample: jest.fn(), }; const mockExternalApiService = { enrichExample: jest.fn(), validateExample: jest.fn(), }; const module: TestingModule = await Test.createTestingModule({ providers: [ ExampleService, { provide: SupabaseDataService, useValue: mockSupabaseService, }, { provide: ExternalApiService, useValue: mockExternalApiService, }, ], }).compile(); service = module.get(ExampleService); supabaseService = module.get(SupabaseDataService) as jest.Mocked; externalApiService = module.get(ExternalApiService) as jest.Mocked; }); afterEach(() => { jest.clearAllMocks(); }); describe('create', () => { const createDto: CreateExampleDto = { title: 'New Example', description: 'New description', }; it('should create an example successfully', async () => { // Arrange const enrichedData = { ...createDto, metadata: { enhanced: true }, }; externalApiService.enrichExample.mockResolvedValue({ data: enrichedData, error: null, }); supabaseService.insertExample.mockResolvedValue({ data: { ...mockExample, ...enrichedData }, error: null, }); // Act const result = await service.create(createDto, mockUser.sub); // Assert expect(result.error).toBeNull(); expect(result.data).toBeDefined(); expect(result.data.title).toBe(createDto.title); expect(externalApiService.enrichExample).toHaveBeenCalledWith(createDto); expect(supabaseService.insertExample).toHaveBeenCalledWith( expect.objectContaining({ ...enrichedData, userId: mockUser.sub, }) ); }); it('should handle enrichment failure gracefully', async () => { // Arrange externalApiService.enrichExample.mockResolvedValue({ data: null, error: new Error('API unavailable'), }); supabaseService.insertExample.mockResolvedValue({ data: { ...mockExample, ...createDto }, error: null, }); // Act const result = await service.create(createDto, mockUser.sub); // Assert - Should still create without enrichment expect(result.error).toBeNull(); expect(result.data).toBeDefined(); expect(supabaseService.insertExample).toHaveBeenCalledWith( expect.objectContaining({ ...createDto, userId: mockUser.sub, }) ); }); it('should return error when database insert fails', async () => { // Arrange externalApiService.enrichExample.mockResolvedValue({ data: createDto, error: null, }); const dbError = new Error('Database connection failed'); supabaseService.insertExample.mockResolvedValue({ data: null, error: dbError, }); // Act const result = await service.create(createDto, mockUser.sub); // Assert expect(result.error).toBeDefined(); expect(result.data).toBeNull(); expect(result.error.message).toContain('Database connection failed'); }); it('should validate title is not empty', async () => { // Arrange const invalidDto = { ...createDto, title: '' }; // Act const result = await service.create(invalidDto, mockUser.sub); // Assert expect(result.error).toBeDefined(); expect(result.error.message).toContain('Title cannot be empty'); expect(externalApiService.enrichExample).not.toHaveBeenCalled(); expect(supabaseService.insertExample).not.toHaveBeenCalled(); }); it('should sanitize user input', async () => { // Arrange const maliciousDto = { title: '', description: 'Normal description', }; externalApiService.enrichExample.mockResolvedValue({ data: maliciousDto, error: null, }); supabaseService.insertExample.mockResolvedValue({ data: { ...mockExample, title: 'alert("xss")' }, // Sanitized error: null, }); // Act const result = await service.create(maliciousDto, mockUser.sub); // Assert expect(result.data.title).not.toContain('