模拟ioRedis连接失败

问题描述 投票:0回答:1

我在生产代码中使用

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')

注意:在模拟从 Redis 获取和设置的所有其他测试中,我使用的是
ioredis-mock
并且它工作得很好。

typescript aws-lambda node-redis ioredis
1个回答
0
投票

我不会像这样测试“错误”事件,所以我不能特别回答你的问题,但这就是我的想法:

当我对速率限制器进行单元测试时,我尝试做类似

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
      })
  })
})

如果我没记错的话,当时我正在阅读一些人对此的抱怨。

最新问题
© www.soinside.com 2019 - 2025. All rights reserved.