/** * RealRedisService集成测试 * * 功能描述: * - 使用真实Redis连接进行集成测试 * - 测试Redis服务器连接和断开 * - 验证数据持久性和一致性 * - 测试Redis服务的完整工作流程 * * 职责分离: * - 集成测试:测试与真实Redis服务器的交互 * - 连接测试:验证Redis连接管理 * - 数据一致性:测试数据的持久化和读取 * - 性能测试:验证Redis操作的性能表现 * * 最近修改: * - 2025-01-07: 功能新增 - 创建RealRedisService完整集成测试,验证真实Redis交互 * * @author moyin * @version 1.0.0 * @since 2025-01-07 * @lastModified 2025-01-07 */ import { Test, TestingModule } from '@nestjs/testing'; import { ConfigService } from '@nestjs/config'; import { RealRedisService } from './real_redis.service'; import Redis from 'ioredis'; describe('RealRedisService Integration', () => { let service: RealRedisService; let module: TestingModule; let configService: ConfigService; // 测试配置 - 使用测试Redis实例 const testRedisConfig = { REDIS_HOST: process.env.TEST_REDIS_HOST || 'localhost', REDIS_PORT: parseInt(process.env.TEST_REDIS_PORT || '6379'), REDIS_PASSWORD: process.env.TEST_REDIS_PASSWORD, REDIS_DB: parseInt(process.env.TEST_REDIS_DB || '15'), // 使用DB 15进行测试 }; beforeAll(async () => { // 检查是否有可用的Redis服务器 const testRedis = new Redis({ host: testRedisConfig.REDIS_HOST, port: testRedisConfig.REDIS_PORT, password: testRedisConfig.REDIS_PASSWORD, db: testRedisConfig.REDIS_DB, lazyConnect: true, maxRetriesPerRequest: 1, }); try { await testRedis.ping(); // 确保连接被正确断开 testRedis.disconnect(false); } catch (error) { console.warn('Redis服务器不可用,跳过集成测试:', (error as Error).message); // 确保连接被正确断开 testRedis.disconnect(false); return; } // 创建测试模块 module = await Test.createTestingModule({ providers: [ RealRedisService, { provide: ConfigService, useValue: { get: jest.fn((key: string, defaultValue?: any) => { return testRedisConfig[key] || defaultValue; }), }, }, ], }).compile(); service = module.get(RealRedisService); configService = module.get(ConfigService); // 清空测试数据库 try { await service.flushall(); } catch (error) { // 忽略清空数据时的错误 } }); afterAll(async () => { if (service) { try { // 清空测试数据 await service.flushall(); } catch (error) { // 忽略清空数据时的错误 } try { // 断开连接 service.onModuleDestroy(); } catch (error) { // 忽略断开连接时的错误 } } if (module) { await module.close(); } }); beforeEach(async () => { // 每个测试前清空数据 if (service) { try { await service.flushall(); } catch (error) { // 忽略清空数据时的错误 } } }); // 检查Redis是否可用的辅助函数 const skipIfRedisUnavailable = () => { if (!service) { return true; // 返回true表示应该跳过测试 } return false; }; describe('Redis连接管理', () => { it('should connect to Redis server successfully', () => { if (skipIfRedisUnavailable()) { console.log('跳过测试:Redis服务器不可用'); return; } expect(service).toBeDefined(); }); it('should use correct Redis configuration', () => { if (skipIfRedisUnavailable()) { console.log('跳过测试:Redis服务器不可用'); return; } expect(configService.get).toHaveBeenCalledWith('REDIS_HOST', 'localhost'); expect(configService.get).toHaveBeenCalledWith('REDIS_PORT', 6379); expect(configService.get).toHaveBeenCalledWith('REDIS_DB', 0); }); }); describe('基础键值操作', () => { it('should set and get string values', async () => { if (skipIfRedisUnavailable()) { console.log('跳过测试:Redis服务器不可用'); return; } await service.set('test:string', 'Hello Redis'); const result = await service.get('test:string'); expect(result).toBe('Hello Redis'); }); it('should handle non-existent keys', async () => { if (skipIfRedisUnavailable()) { console.log('跳过测试:Redis服务器不可用'); return; } const result = await service.get('test:nonexistent'); expect(result).toBeNull(); }); it('should delete existing keys', async () => { if (skipIfRedisUnavailable()) { console.log('跳过测试:Redis服务器不可用'); return; } await service.set('test:delete', 'to be deleted'); const deleted = await service.del('test:delete'); const result = await service.get('test:delete'); expect(deleted).toBe(true); expect(result).toBeNull(); }); it('should return false when deleting non-existent keys', async () => { if (skipIfRedisUnavailable()) { console.log('跳过测试:Redis服务器不可用'); return; } const deleted = await service.del('test:nonexistent'); expect(deleted).toBe(false); }); it('should check key existence', async () => { if (skipIfRedisUnavailable()) { console.log('跳过测试:Redis服务器不可用'); return; } await service.set('test:exists', 'exists'); const exists = await service.exists('test:exists'); const notExists = await service.exists('test:notexists'); expect(exists).toBe(true); expect(notExists).toBe(false); }); }); describe('过期时间管理', () => { it('should set keys with TTL', async () => { if (skipIfRedisUnavailable()) { console.log('跳过测试:Redis服务器不可用'); return; } await service.set('test:ttl', 'expires soon', 2); const ttl = await service.ttl('test:ttl'); expect(ttl).toBeGreaterThan(0); expect(ttl).toBeLessThanOrEqual(2); }); it('should expire keys after TTL', async () => { if (skipIfRedisUnavailable()) { console.log('跳过测试:Redis服务器不可用'); return; } await service.set('test:expire', 'will expire', 1); // 等待过期 await new Promise(resolve => setTimeout(resolve, 1100)); const result = await service.get('test:expire'); expect(result).toBeNull(); }, 2000); it('should set expiration on existing keys', async () => { if (skipIfRedisUnavailable()) { console.log('跳过测试:Redis服务器不可用'); return; } await service.set('test:expire_later', 'set expiration later'); await service.expire('test:expire_later', 2); const ttl = await service.ttl('test:expire_later'); expect(ttl).toBeGreaterThan(0); expect(ttl).toBeLessThanOrEqual(2); }); it('should return -1 for keys without expiration', async () => { if (skipIfRedisUnavailable()) { console.log('跳过测试:Redis服务器不可用'); return; } await service.set('test:no_expire', 'never expires'); const ttl = await service.ttl('test:no_expire'); expect(ttl).toBe(-1); }); it('should return -2 for non-existent keys', async () => { if (skipIfRedisUnavailable()) { console.log('跳过测试:Redis服务器不可用'); return; } const ttl = await service.ttl('test:nonexistent'); expect(ttl).toBe(-2); }); it('should use setex for atomic set with expiration', async () => { if (skipIfRedisUnavailable()) { console.log('跳过测试:Redis服务器不可用'); return; } await service.setex('test:setex', 2, 'atomic set with expiration'); const value = await service.get('test:setex'); const ttl = await service.ttl('test:setex'); expect(value).toBe('atomic set with expiration'); expect(ttl).toBeGreaterThan(0); expect(ttl).toBeLessThanOrEqual(2); }); }); describe('数值操作', () => { it('should increment numeric values', async () => { if (skipIfRedisUnavailable()) { console.log('跳过测试:Redis服务器不可用'); return; } const result1 = await service.incr('test:counter'); const result2 = await service.incr('test:counter'); const result3 = await service.incr('test:counter'); expect(result1).toBe(1); expect(result2).toBe(2); expect(result3).toBe(3); }); it('should increment existing numeric values', async () => { if (skipIfRedisUnavailable()) { console.log('跳过测试:Redis服务器不可用'); return; } await service.set('test:existing_counter', '10'); const result = await service.incr('test:existing_counter'); expect(result).toBe(11); }); }); describe('集合操作', () => { it('should add and retrieve set members', async () => { if (skipIfRedisUnavailable()) { console.log('跳过测试:Redis服务器不可用'); return; } await service.sadd('test:set', 'member1'); await service.sadd('test:set', 'member2'); await service.sadd('test:set', 'member3'); const members = await service.smembers('test:set'); expect(members).toHaveLength(3); expect(members).toContain('member1'); expect(members).toContain('member2'); expect(members).toContain('member3'); }); it('should remove set members', async () => { if (skipIfRedisUnavailable()) { console.log('跳过测试:Redis服务器不可用'); return; } await service.sadd('test:set_remove', 'member1'); await service.sadd('test:set_remove', 'member2'); await service.sadd('test:set_remove', 'member3'); await service.srem('test:set_remove', 'member2'); const members = await service.smembers('test:set_remove'); expect(members).toHaveLength(2); expect(members).toContain('member1'); expect(members).toContain('member3'); expect(members).not.toContain('member2'); }); it('should return empty array for non-existent sets', async () => { if (skipIfRedisUnavailable()) { console.log('跳过测试:Redis服务器不可用'); return; } const members = await service.smembers('test:nonexistent_set'); expect(members).toEqual([]); }); it('should handle duplicate set members', async () => { if (skipIfRedisUnavailable()) { console.log('跳过测试:Redis服务器不可用'); return; } await service.sadd('test:duplicate_set', 'member1'); await service.sadd('test:duplicate_set', 'member1'); // 重复添加 await service.sadd('test:duplicate_set', 'member2'); const members = await service.smembers('test:duplicate_set'); expect(members).toHaveLength(2); expect(members).toContain('member1'); expect(members).toContain('member2'); }); }); describe('数据持久性和一致性', () => { it('should persist data across operations', async () => { if (skipIfRedisUnavailable()) { console.log('跳过测试:Redis服务器不可用'); return; } // 设置多种类型的数据 await service.set('test:persist:string', 'persistent string'); await service.set('test:persist:number', '42'); await service.sadd('test:persist:set', 'set_member'); await service.incr('test:persist:counter'); // 验证数据持久性 const stringValue = await service.get('test:persist:string'); const numberValue = await service.get('test:persist:number'); const setMembers = await service.smembers('test:persist:set'); const counterValue = await service.get('test:persist:counter'); expect(stringValue).toBe('persistent string'); expect(numberValue).toBe('42'); expect(setMembers).toContain('set_member'); expect(counterValue).toBe('1'); }); it('should maintain data consistency during concurrent operations', async () => { if (skipIfRedisUnavailable()) { console.log('跳过测试:Redis服务器不可用'); return; } // 并发执行多个操作 const promises = []; for (let i = 0; i < 10; i++) { promises.push(service.incr('test:concurrent:counter')); promises.push(service.sadd('test:concurrent:set', `member${i}`)); } await Promise.all(promises); // 验证结果一致性 const counterValue = await service.get('test:concurrent:counter'); const setMembers = await service.smembers('test:concurrent:set'); expect(parseInt(counterValue)).toBe(10); expect(setMembers).toHaveLength(10); }); }); describe('清空操作', () => { it('should clear all data with flushall', async () => { if (skipIfRedisUnavailable()) { console.log('跳过测试:Redis服务器不可用'); return; } // 设置一些测试数据 await service.set('test:flush1', 'value1'); await service.set('test:flush2', 'value2'); await service.sadd('test:flush_set', 'member'); // 清空所有数据 await service.flushall(); // 验证数据已清空 const value1 = await service.get('test:flush1'); const value2 = await service.get('test:flush2'); const setMembers = await service.smembers('test:flush_set'); expect(value1).toBeNull(); expect(value2).toBeNull(); expect(setMembers).toEqual([]); }); }); describe('错误处理和边界情况', () => { it('should handle empty string keys and values', async () => { if (skipIfRedisUnavailable()) { console.log('跳过测试:Redis服务器不可用'); return; } await service.set('', 'empty key'); await service.set('empty_value', ''); const emptyKeyValue = await service.get(''); const emptyValue = await service.get('empty_value'); expect(emptyKeyValue).toBe('empty key'); expect(emptyValue).toBe(''); }); it('should handle very long keys and values', async () => { if (skipIfRedisUnavailable()) { console.log('跳过测试:Redis服务器不可用'); return; } const longKey = 'test:' + 'a'.repeat(1000); const longValue = 'b'.repeat(10000); await service.set(longKey, longValue); const result = await service.get(longKey); expect(result).toBe(longValue); }); it('should handle special characters in keys and values', async () => { if (skipIfRedisUnavailable()) { console.log('跳过测试:Redis服务器不可用'); return; } const specialKey = 'test:特殊字符:🚀:key'; const specialValue = 'Special value with 特殊字符 and 🎉 emojis'; await service.set(specialKey, specialValue); const result = await service.get(specialKey); expect(result).toBe(specialValue); }); }); describe('性能测试', () => { it('should handle multiple operations efficiently', async () => { if (skipIfRedisUnavailable()) { console.log('跳过测试:Redis服务器不可用'); return; } const startTime = Date.now(); const operations = 100; // 执行大量操作 const promises = []; for (let i = 0; i < operations; i++) { promises.push(service.set(`test:perf:${i}`, `value${i}`)); } await Promise.all(promises); // 读取所有数据 const readPromises = []; for (let i = 0; i < operations; i++) { readPromises.push(service.get(`test:perf:${i}`)); } const results = await Promise.all(readPromises); const endTime = Date.now(); const duration = endTime - startTime; // 验证结果正确性 expect(results).toHaveLength(operations); results.forEach((result, index) => { expect(result).toBe(`value${index}`); }); // 性能检查(应该在合理时间内完成) expect(duration).toBeLessThan(5000); // 5秒内完成 }, 10000); }); });