stipsan / ioredis-mock

Emulates ioredis by performing all operations in-memory.
MIT License
339 stars 123 forks source link

It doesn't trigger any events on creating a new connection to capture at `on` methods #1351

Open SeyyedKhandon opened 1 month ago

SeyyedKhandon commented 1 month ago

I'm trying to mock ioredis so to be able to test it, but unfortunately, am not able to do it properly, I triedioredis-mock but it doesnt trigger any event on ready, so it does not work for me or I dont know how to do it properly, please someone shed some light on it

const createCacheInstance = async () => {
  const address = process.env.REDIS_CLUSTER_ADDRESS
  const password = process.env.REDIS_AUTH_TOKEN

  logger.info(`Redis cluster env address:${address}`)

  try {
    if (!address || !password)
      throw new Error('Environment variables {REDIS_CLUSTER_ADDRESS or REDIS_AUTH_TOKEN} are not available')

    const host = address.split(':')[0]
    const port = Number(address.split(':')[1])
    logger.info(`Redis cluster host:${host} port:${port}`)

    const tlsMode = process.env.NODE_ENV === 'production' ? { tls: {} } : {} // https://github.com/redis/ioredis?tab=readme-ov-file#special-note-aws-elasticache-clusters-with-tls

    const redisCluster = new Redis.Cluster([{ host, port }], {
      slotsRefreshTimeout: 2000, // to prevent [Failed to refresh slots cache error](https://github.com/redis/ioredis/issues/711)
      scaleReads: 'all', // Send write queries to masters and read queries to masters or slaves randomly.
      dnsLookup: (address, callback) => callback(null, address), // https://github.com/redis/ioredis?tab=readme-ov-file#special-note-aws-elasticache-clusters-with-tls
      redisOptions: { password, ...tlsMode },
    })

    return await new Promise<{ redisCluster?: Cluster; error?: Error }>((resolve, reject) => {
      redisCluster
        .on('connecting', () => {
          cacheStatus = redisCluster.status
          logger.info('Connecting to Redis DB')
        })
        .on('connect', () => {
          cacheStatus = redisCluster.status
          logger.info('Connected to Redis DB')
        })
        .on('ready', () => {
          cacheStatus = redisCluster.status
          logger.info('Redis cache instance is ready to use')
          resolve({ redisCluster })
        })
        .on('end', () => {
          cacheStatus = redisCluster.status
          logger.warn('Redis cache connection has been closed')
        })
        .on('error', async (error) => {
          cacheStatus = redisCluster.status
          cacheError = error
          logger.error(error)
          try {
            await redisCluster.quit()
          } catch (e) {
            logger.error({ message: 'Error while closing the Redis cache connection', e })
          }
          reject({ error })
        })
    })
  } catch (e) {
    const error = e as Error
    logger.error(error)
    cacheStatus = 'error'
    cacheError = error
    return { error }
  }
}

What I tried which results in "Connection is closed." "ClusterAllFailedError: Failed to refresh slots cache. which seems I need to mock the connection itself too, but am not sure how?

import { afterAll, describe, expect, it, vi } from 'vitest'
import cacheManager, { useCache } from './index'
import { useHashTagKeyMaker } from './helper'

const mocks = vi.hoisted(() => {
  return {
    redisCluster: vi.fn().mockImplementation(() => ({
      set: vi.fn(),
      get: vi.fn(),
      exists: vi.fn(),
      del: vi.fn(),
      sadd: vi.fn(),
      smembers: vi.fn(),
      srem: vi.fn(),
      scard: vi.fn(),
      smismember: vi.fn(),
      flushdb: vi.fn(),
      nodes: vi.fn().mockReturnValue([{ flushdb: vi.fn() }]),
      on: vi.fn().mockImplementation((event, callback) => {
        if (event === 'ready') {
          callback()
        }
        return this
      }),
      quit: vi.fn(),
      status: 'ready',
    })),
  }
})

vi.mock('ioredis', async () => {
  const actual = await vi.importActual('ioredis')
  return {
    ...actual,
    Cluster: mocks.redisCluster,
  }
})