Skip to content

Testing Utilities

An in-memory Redis driver for unit-testing NestJS RedisX code — no Redis required.

Overview

@nestjs-redisx/testing provides a drop-in 'memory' driver that implements the same IRedisDriver contract as the ioredis and node-redis adapters. Your plugins (CachePlugin, LocksPlugin, RateLimitPlugin, IdempotencyPlugin, StreamsPlugin) run with their real production code — including their Lua scripts and stream consumer groups — against an in-memory keyspace. Tests stay fast, deterministic, and isolated.

ConcernReal Redis in tests@nestjs-redisx/testing
InfrastructureContainer / service requiredNone — pure in-memory
SpeedNetwork round-tripsSynchronous, microsecond
IsolationShared keyspace, flush neededFresh keyspace per driver
Lua scriptsRun on RedisRun on a built-in interpreter
DeterminismTime/ordering quirksFully controllable

Key Features

  • Real plugin behavior — exercises the actual cache/locks/rate-limit/idempotency/streams code paths, not mocks.
  • Lua execution — a small, in-house Lua interpreter runs the plugins' atomic scripts (token bucket, lock release, etc.).
  • Zero dependencies — no third-party Redis mock; nothing extra in your runtime.
  • Same IRedisDriver — strings, hashes, sets, sorted sets, lists, keys/TTL, and scripting.
  • Drop-in — switch the driver with one option, or use the RedisTestingModule wrapper.

Installation

bash
npm install -D @nestjs-redisx/testing

It is a devDependency — the in-memory driver is only for tests.

Quick Start

Use the RedisTestingModule wrapper to force the in-memory driver and register the same plugins you use in production:

typescript
import { Module } from '@nestjs/common';
import { CachePlugin } from '@nestjs-redisx/cache';
import { RateLimitPlugin } from '@nestjs-redisx/rate-limit';
import { RedisTestingModule } from '@nestjs-redisx/testing';

/**
 * `RedisTestingModule` is an ergonomic wrapper around `RedisModule` that forces
 * the in-memory driver for you — no `global.driver` or `clients` boilerplate.
 * Register the same plugins you use in production to test their real behavior.
 */
@Module({
  imports: [
    RedisTestingModule.forRoot({
      plugins: [new CachePlugin(), new RateLimitPlugin({ defaultAlgorithm: 'token-bucket', defaultPoints: 5, defaultDuration: 60 })],
    }),
  ],
})
export class TestAppModule {}

Then boot a Nest context and assert on the real service behavior:

typescript
import { NestFactory } from '@nestjs/core';
import { CachePlugin, CACHE_SERVICE, ICacheService } from '@nestjs-redisx/cache';
import { RedisTestingModule } from '@nestjs-redisx/testing';

/**
 * Boots a Nest context backed by the in-memory driver and exercises the real
 * CacheService. No Redis runs — `getOrSet` invokes the loader once and serves
 * the cached value on the second call. Returns the loader call count (1).
 */
export async function cacheLoadsOnce(): Promise<number> {
  const app = await NestFactory.createApplicationContext(RedisTestingModule.forRoot({ plugins: [new CachePlugin()] }), { logger: false });

  const cache = app.get<ICacheService>(CACHE_SERVICE);

  let calls = 0;
  const loader = async (): Promise<{ id: number }> => {
    calls += 1;
    return { id: 1 };
  };

  await cache.getOrSet('user:1', loader, { ttl: 60 });
  await cache.getOrSet('user:1', loader, { ttl: 60 });

  await app.close();
  return calls; // 1
}

How It Works

The plugins are driver-agnostic — they only depend on IRedisDriver. Selecting the 'memory' driver swaps the transport; everything above it is unchanged.

When to Use It — and When to Use Real Redis

The in-memory driver answers "is my code correct?" — fast, on every commit, with no infrastructure. It is not a full Redis emulator: questions about Redis's own behavior still belong in integration tests against a real Redis.

✅ Test with the in-memory driver🔺 Test against real Redis
Cache hit/miss, TTL, stampede, tag invalidationCluster cross-slot routing & hash-tag correctness
Lock acquisition / contention / releaseSharded Pub/Sub & cross-process fan-out
Rate-limit algorithms (token bucket, windows)Sentinel failover & reconnection behavior
Idempotency check-and-lock, replay, fingerprintReal network latency / throughput / load
Streams produce → consumer group → ack / claimExact BLOCK timeout / long-poll timing
Pub/Sub publish → @Subscribe handlers (in-process bus)

Known limitations

Be aware of what the in-memory driver intentionally does not simulate:

  • Single-node semantics — one keyspace; no SELECT, no cluster cross-slot (CROSSSLOT) checks or hash-tag routing. A missing hash-tag bug passes in-memory but can fail on a real cluster.
  • Pub/Sub is process-wide — publish/subscribe works via an in-process bus with single-node semantics (real cross-connection delivery inside one test process, but not across processes or shards).
  • Blocking reads return promptlyBLOCK on XREADGROUP/XREAD does not wait the full timeout. Delivery is still correct; only the timing differs.
  • Correctness tool, not a performance simulator — don't use it for load or latency testing.
  • Unsupported commands fail loudly — an unimplemented command throws MemoryDriverError instead of silently returning a wrong result.

For any of the above, use a real Redis — the project ships integration test configs for standalone, Sentinel, and Cluster.

Documentation

TopicDescription
ConfigurationDriver selection, RedisTestingModule, and options
In-Memory DriverSupported commands, the Lua subset, and limitations
Testing PluginsPatterns for cache, locks, rate-limit, idempotency, streams
TroubleshootingCommon errors and how to resolve them

Released under the MIT License.