import { Test, TestingModule } from '@nestjs/testing'; import { TypeOrmModule } from '@nestjs/typeorm'; import { ConfigModule, ConfigService } from '@nestjs/config'; import { getRepositoryToken } from '@nestjs/typeorm'; import { Repository, Like } from 'typeorm'; import { UserService } from '../../../src/modules/user/user.service'; import { User } from '../../../src/modules/user/user.entity'; import { UserModule } from '../../../src/modules/user/user.module'; import { CreateUserDto } from '../../../src/modules/user/dto/create-user.dto'; import { getDatabaseConfig } from '../../../src/database/database.config'; import { ConflictException } from '@nestjs/common'; describe('UserService (集成测试)', () => { let service: UserService; let repository: Repository; let module: TestingModule; beforeAll(async () => { jest.setTimeout(30000); // 设置超时为 30 秒 // 创建测试模块,使用真实数据库连接 module = await Test.createTestingModule({ imports: [ ConfigModule.forRoot({ isGlobal: true, envFilePath: ['.env.development', '.env'], }), TypeOrmModule.forRootAsync({ imports: [ConfigModule], useFactory: (configService: ConfigService) => { const config = getDatabaseConfig(configService); // 使用测试数据库(如果配置了),否则使用开发数据库 const testDatabase = configService.get('DB_DATABASE') || (config.database as string); return { ...config, database: testDatabase, synchronize: true, // 测试环境允许同步 dropSchema: false, // 不删除现有数据 } as typeof config; }, inject: [ConfigService], }), UserModule, ], }).compile(); service = module.get(UserService); repository = module.get>(getRepositoryToken(User)); // 不清理原有数据,只清理可能存在的测试数据 // 使用测试数据前缀来识别和清理测试数据 await repository.delete({ username: Like('test_%'), }); await repository.delete({ username: Like('findall_%'), }); await repository.delete({ username: Like('duplicate_%'), }); await repository.delete({ username: Like('unique_%'), }); await repository.delete({ username: Like('full_%'), }); }); afterAll(async () => { // 测试结束后清理 // await repository.clear(); await module.close(); }); describe('create - 集成测试', () => { it('应该成功在数据库中创建用户', async () => { const createUserDto: CreateUserDto = { username: 'test_user_1', password: 'password123', email: 'test1@example.com', nickname: '测试用户1', }; const result = await service.create(createUserDto); console.log('regitser result', result); expect(result).toBeDefined(); expect(result.userId).toBeDefined(); expect(result.username).toBe(createUserDto.username); expect(result.email).toBe(createUserDto.email); expect(result.nickname).toBe(createUserDto.nickname); expect(result.status).toBe('active'); expect(result.role).toBe('user'); // 默认角色 expect(result.passwordHash).toBeDefined(); expect(result.passwordHash).not.toBe(createUserDto.password); // 密码应该被加密 // 验证数据确实保存到数据库 const savedUser = await repository.findOne({ where: { userId: result.userId }, }); expect(savedUser).toBeDefined(); expect(savedUser?.username).toBe(createUserDto.username); expect(savedUser?.email).toBe(createUserDto.email); // 清理 await repository.remove(result); }); it('应该使用默认角色和状态', async () => { const createUserDto: CreateUserDto = { username: 'test_user_2', password: 'password123', email: 'test2@example.com', // 不提供 role }; const result = await service.create(createUserDto); expect(result.role).toBe('user'); // 默认角色 expect(result.status).toBe('active'); // 默认状态 // 清理测试创建的用户 await repository.remove(result); }); it('应该支持可选字段', async () => { const createUserDto: CreateUserDto = { username: 'test_user_3', password: 'password123', email: 'test3@example.com', nickname: '测试用户3', avatarUrl: 'https://example.com/avatar.jpg', phone: '13800138000', }; const result = await service.create(createUserDto); expect(result.nickname).toBe(createUserDto.nickname); expect(result.avatarUrl).toBe(createUserDto.avatarUrl); expect(result.phone).toBe(createUserDto.phone); // 清理测试创建的用户 await repository.remove(result); }); it('应该抛出 ConflictException 当用户名已存在时', async () => { const createUserDto: CreateUserDto = { username: 'duplicate_username', password: 'password123', email: 'unique@example.com', }; // 先创建一个用户 await service.create(createUserDto); // 尝试创建相同用户名的用户 const duplicateDto: CreateUserDto = { username: 'duplicate_username', password: 'password456', email: 'another@example.com', }; await expect(service.create(duplicateDto)).rejects.toThrow( ConflictException, ); await expect(service.create(duplicateDto)).rejects.toThrow( '用户名', ); // 清理测试创建的用户 const user = await repository.findOne({ where: { username: 'duplicate_username' }, }); if (user) { await repository.remove(user); } }); it('应该抛出 ConflictException 当邮箱已存在时', async () => { const createUserDto: CreateUserDto = { username: 'unique_username', password: 'password123', email: 'duplicate@example.com', }; // 先创建一个用户 await service.create(createUserDto); // 尝试创建相同邮箱的用户 const duplicateDto: CreateUserDto = { username: 'another_username', password: 'password456', email: 'duplicate@example.com', }; await expect(service.create(duplicateDto)).rejects.toThrow( ConflictException, ); await expect(service.create(duplicateDto)).rejects.toThrow('邮箱'); // 清理测试创建的用户 const user = await repository.findOne({ where: { email: 'duplicate@example.com' }, }); if (user) { await repository.remove(user); } }); }); describe('findAll - 集成测试', () => { it('应该返回所有用户列表', async () => { // 先创建几个测试用户 const users: CreateUserDto[] = [ { username: 'findall_user_1', password: 'password123', email: 'findall1@example.com', nickname: '查询测试用户1', }, { username: 'findall_user_2', password: 'password123', email: 'findall2@example.com', nickname: '查询测试用户2', }, { username: 'findall_user_3', password: 'password123', email: 'findall3@example.com', nickname: '查询测试用户3', // 注意:注册时不允许传入 role,所有注册用户的 role 固定为 'user' }, ]; const createdUsers: User[] = []; for (const userDto of users) { const user = await service.create(userDto); createdUsers.push(user); } // 查询所有用户 const result = await service.findAll(); expect(result).toBeDefined(); expect(Array.isArray(result)).toBe(true); expect(result.length).toBeGreaterThanOrEqual(users.length); // 验证创建的用户都在列表中 const usernames = result.map((u) => u.username); users.forEach((userDto) => { expect(usernames).toContain(userDto.username); }); // 验证排序(按创建时间倒序) if (result.length > 1) { for (let i = 0; i < result.length - 1; i++) { expect( result[i].createdAt.getTime(), ).toBeGreaterThanOrEqual(result[i + 1].createdAt.getTime()); } } // 清理测试创建的用户 for (const user of createdUsers) { await repository.remove(user); } }); it('应该返回空数组当没有用户时', async () => { // 注意:这个测试假设数据库中至少有一些用户 // 如果数据库为空,这个测试会失败 // 为了不破坏原有数据,我们只验证返回的是数组 const result = await service.findAll(); expect(result).toBeDefined(); expect(Array.isArray(result)).toBe(true); // 不验证长度为0,因为可能已有其他用户数据 // expect(result.length).toBe(0); }); it('应该包含用户的所有字段', async () => { // 创建一个完整信息的用户 const createUserDto: CreateUserDto = { username: 'full_info_user', password: 'password123', email: 'fullinfo@example.com', nickname: '完整信息用户', avatarUrl: 'https://example.com/avatar.jpg', phone: '13800138000', // 注意:注册时不允许传入 role,所有注册用户的 role 固定为 'user' }; await service.create(createUserDto); // 查询所有用户 const result = await service.findAll(); const foundUser = result.find( (u) => u.username === createUserDto.username, ); expect(foundUser).toBeDefined(); expect(foundUser?.userId).toBeDefined(); expect(foundUser?.username).toBe(createUserDto.username); expect(foundUser?.email).toBe(createUserDto.email); expect(foundUser?.nickname).toBe(createUserDto.nickname); expect(foundUser?.avatarUrl).toBe(createUserDto.avatarUrl); expect(foundUser?.phone).toBe(createUserDto.phone); expect(foundUser?.role).toBe('user'); // 注册用户的 role 固定为 'user' expect(foundUser?.status).toBe('active'); expect(foundUser?.createdAt).toBeDefined(); expect(foundUser?.updatedAt).toBeDefined(); // 清理测试创建的用户 if (foundUser) { await repository.remove(foundUser); } }); }); });