如何在Express API测试中Mock基于ioredis的Redis ES6类?
Mock Redis集群类进行Express API测试(Jest/TS-Jest + Supertest)
下面分两种常用方案解决你替换ioredis为mock版本的问题,直接匹配你的开发场景:
方案1:手动Mock RedisClusterClient类(灵活控制方法行为)
如果你的Redis集群类是单独模块导出的(比如src/redis/RedisClusterClient.ts),直接用Jest的模块Mock功能替换整个类,自定义方法的返回逻辑。
假设你的Redis集群类结构:
// src/redis/RedisClusterClient.ts import Redis from 'ioredis'; export class RedisClusterClient { private cluster: Redis.Cluster; constructor(nodes: Redis.ClusterNode[]) { this.cluster = new Redis.Cluster(nodes); } async get(key: string): Promise<string | null> { return this.cluster.get(key); } async set(key: string, value: string): Promise<string> { return this.cluster.set(key, value); } }
测试文件实现:
// src/routes/user.test.ts import request from 'supertest'; import express from 'express'; import userRouter from './user'; import { RedisClusterClient } from '../redis/RedisClusterClient'; // Mock整个RedisClusterClient模块 jest.mock('../redis/RedisClusterClient', () => ({ RedisClusterClient: jest.fn().mockImplementation(() => ({ get: jest.fn(), set: jest.fn(), })), })); const app = express(); app.use(express.json()); app.use(userRouter); describe('GET /users/:id', () => { let mockRedisClient: jest.Mocked<RedisClusterClient>; // 每次测试前重置mock,避免测试污染 beforeEach(() => { (RedisClusterClient as jest.Mock).mockClear(); mockRedisClient = (RedisClusterClient as jest.Mock).mock.instances[0] as jest.Mocked<RedisClusterClient>; }); it('返回200和用户数据当用户存在时', async () => { const mockUser = { id: '123', name: 'Chris' }; // 模拟get方法返回序列化后的用户数据 mockRedisClient.get.mockResolvedValue(JSON.stringify(mockUser)); const res = await request(app).get('/users/123'); expect(res.status).toBe(200); expect(res.body).toEqual(mockUser); expect(mockRedisClient.get).toHaveBeenCalledExactlyOnceWith('user:123'); }); it('返回404当用户不存在时', async () => { mockRedisClient.get.mockResolvedValue(null); const res = await request(app).get('/users/456'); expect(res.status).toBe(404); expect(res.body.message).toBe('User not found'); }); });
方案2:用ioredis-mock替换真实ioredis(模拟真实Redis行为)
如果需要模拟Redis的真实操作逻辑(比如自动处理过期、哈希结构等),可以用ioredis-mock直接替换ioredis模块。
第一步:安装依赖
npm install --save-dev ioredis-mock
测试文件实现:
// src/routes/user.test.ts import request from 'supertest'; import express from 'express'; import userRouter from './user'; import Redis from 'ioredis'; import RedisMock from 'ioredis-mock'; // Mock ioredis,用ioredis-mock的Cluster替换真实Cluster jest.mock('ioredis', () => { const original = jest.requireActual('ioredis'); return { ...original, Cluster: RedisMock.Cluster, }; }); const app = express(); app.use(express.json()); app.use(userRouter); describe('GET /users/:id', () => { let mockCluster: Redis.Cluster; beforeEach(async () => { // 初始化mock集群并清空数据 mockCluster = new Redis.Cluster([{ host: 'localhost', port: 6379 }]); await mockCluster.flushall(); }); it('返回200和用户数据当用户存在时', async () => { const mockUser = { id: '123', name: 'Chris' }; // 先往mock集群写入数据 await mockCluster.set('user:123', JSON.stringify(mockUser)); const res = await request(app).get('/users/123'); expect(res.status).toBe(200); expect(res.body).toEqual(mockUser); }); it('返回404当用户不存在时', async () => { const res = await request(app).get('/users/456'); expect(res.status).toBe(404); expect(res.body.message).toBe('User not found'); }); });
关键注意点
- 若你的Redis客户端是通过依赖注入(比如放在Express的
req.app.locals或用IoC容器),测试时直接传入mock实例即可,无需Mock模块,这种方式耦合度更低。 - TypeScript环境下,用
jest.Mocked<T>来包装mock对象的类型,避免类型报错。 - 务必在
beforeEach里重置mock或清空mock数据,防止测试用例之间互相干扰。
内容的提问来源于stack exchange,提问作者Chris Rowles
相关产品推荐
相关产品推荐

