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.
| Concern | Real Redis in tests | @nestjs-redisx/testing |
|---|---|---|
| Infrastructure | Container / service required | None — pure in-memory |
| Speed | Network round-trips | Synchronous, microsecond |
| Isolation | Shared keyspace, flush needed | Fresh keyspace per driver |
| Lua scripts | Run on Redis | Run on a built-in interpreter |
| Determinism | Time/ordering quirks | Fully 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
RedisTestingModulewrapper.
Installation
npm install -D @nestjs-redisx/testingIt 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:
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:
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 invalidation | Cluster cross-slot routing & hash-tag correctness |
| Lock acquisition / contention / release | Sharded Pub/Sub & cross-process fan-out |
| Rate-limit algorithms (token bucket, windows) | Sentinel failover & reconnection behavior |
| Idempotency check-and-lock, replay, fingerprint | Real network latency / throughput / load |
| Streams produce → consumer group → ack / claim | Exact 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 promptly —
BLOCKonXREADGROUP/XREADdoes 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
MemoryDriverErrorinstead 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
| Topic | Description |
|---|---|
| Configuration | Driver selection, RedisTestingModule, and options |
| In-Memory Driver | Supported commands, the Lua subset, and limitations |
| Testing Plugins | Patterns for cache, locks, rate-limit, idempotency, streams |
| Troubleshooting | Common errors and how to resolve them |