我在生产代码中使用
ioredis
npm 库:
文件:redisManager.ts
import Redis from 'ioredis';
import EventEmitter from "events";
import {Logger} from "@aws-lambda-powertools/logger";
const logger: Logger= new Logger({serviceName: 'redisManager'});
export class RedisManager extends EventEmitter {
protected readonly redisClient: Redis;
constructor() {
super();
this.redisClient = new Redis(Number(process.env.REDIS_PORT), process.env.REDIS_HOST);
this.redisClient.on('error', err => {
logger.error(`Got an error from Redis: ${err.message}`);
this.emit('error', err);
});
}
getRedisClient(): Redis {
return this.redisClient;
}
waitForRedisConnection = (): Promise<void> => {
return new Promise<void>((resolve, reject) => {
this.redisClient.on('connect', () => logger.info(`Got "connect" event from Redis. Stream is connected to the server`));
this.redisClient.on('ready', () => {
logger.info(`Got "ready" event from Redis. Connection established.`);
resolve();
});
this.redisClient.on('error', async (err) => {
logger.error(`Got "error" event from Redis. Details: [${err.message}]`);
reject(err);
});
});
}
async addKey(redisKey : string, ttl: number) {
return this.redisClient.set(redisKey, 'The value', "PX", ttl);
}
}
export async function getRedisManager(): Promise<RedisManager> {
try {
const redisManager = new RedisManager();
await redisManager.waitForRedisConnection();
} catch (err) {
logger.error(`Get Redis manager failed with error: ${err.message}`);
throw err;
}
}
}
文件app.ts
export const lambdaHandler = async (event: SQSEvent, context: Context): Promise<SQSBatchResponse> => {
context.callbackWaitsForEmptyEventLoop = false;
const batchFailures: SQSBatchResponse = {
"batchItemFailures": []
};
try {
const redisManager: RedisManager = await getRedisManager();
await Promise.all(event.Records.map(async (sqsRecord: SQSRecord) => {
try {
const settingConfig = JSON.parse(sqsRecord.body) as SettingsConfig;
await redisManager.addKey(settingConfig, 333333);
} catch (e) {
const error = e as Error;
logger.error(`Lambda failed. on messageId=${sqsRecord.messageId}: ${error.message}`);
batchFailures.batchItemFailures.push({itemIdentifier: sqsRecord.messageId})
}
}));
logger.info(SUCCESS_MESSAGE);
} catch (e) {
const error = e as Error;
logger.error(`Lambda execution failed. Got error: ${error.message}`);
throw error;
} finally {
logger.info(JSON.stringify(batchFailures, null, 2));
return batchFailures;
}
};
我想通过 jest 连接 Redis 时模拟连接失败。
import {beforeAll, beforeEach, describe, expect, it, jest} from '@jest/globals';
import RedisMock from 'ioredis-mock';
import Redis from "ioredis";
jest.mock('ioredis', () => jest.requireActual('ioredis-mock'));
describe('lambdaHandler()', () => {
it('Redis connection fail ', async () => {
const result: SQSBatchResponse = await lambdaHandler(sqsTriggerEvent, CONTEXT);
}
}
如何在单元测试中触发
redisClient.on('error')
?
ioredis-mock
并且它工作得很好。
我不会像这样测试“错误”事件,所以我不能特别回答你的问题,但这就是我的想法:
当我对速率限制器进行单元测试时,我尝试做类似
ioredis.disconnect()
的事情 - 我试图模拟 Redis 宕机;以及限制器如何优雅地处理该流量。
那次测试绝对是一次 PITA 测试——非常尴尬和不稳定。我记得我对 Redis 如何以及何时真正断开连接感到困惑。
当我设法让它工作时,我在其他地方进行了不稳定的测试 - 因为速率限制器也是我的集成测试的一部分;即使我再次明确
connect
,redis 断开连接也开始失败。
我发现它有一些竞争条件并放弃了。
当我回过头来时,我想我可以对一些命令进行猴子修补并在其中抛出异常,如下所示:
const setFn = ioredis.set
redis.set = () => new Promise((resolve, reject) => {
setTimeout(() => reject(new Error('Simulated redis error')), 0)
})
// when you call:
redis.set('myKeys!', 'gimmeMyKeys!')
// oops! exception!
工作起来轻而易举。简单,没问题 - 除非我弄错了,这正是 Redis 会在你面前爆炸的方式。
您调用某个命令是因为您没有意识到它已断开连接,因此它会抛出异常。
完成后不要忘记撤消猴子补丁。
这是我的测试,使用 Mocha:
// Testing a status endpoint that should return 504 if any service,
// i.e Redis - is down.
//
// The endpoint does a sample `io.set('foo', 'bar')` to test whether
// Redis is alive.
describe('when redis commands are throwing errors', function () {
before(function() {
this.setFn = ioredis.set
ioredis.set = () => new Promise((resolve, reject) => {
setTimeout(() => reject(new Error('Simulated redis error')), 25)
})
})
after(function() {
ioredis.set = this.setFn
})
it('sends status=504 and empty response body', function () {
return chai.request(app)
.head('/status')
.set('authorization', 'Bearer st-4.tu5')
.then(res => {
res.status.should.equal(504)
res.body.should.be.empty
})
})
})
这是另一个测试,我也在测试Redis挂起;而不是抛出异常:
describe('when redis is not responding', function () {
before(function() {
this.setFn = ioredis.set
ioredis.set = () => new Promise(resolve => {})
})
after(function() {
ioredis.set = this.setFn
})
it('sends status=504 and empty response body', function () {
return chai.request(app)
.head('/status')
.set('authorization', 'Bearer st-4.tu5')
.then(res => {
res.status.should.equal(504)
res.body.should.be.empty
})
})
})
如果我没记错的话,当时我正在阅读一些人对此的抱怨。