In-Memory Driver
MemoryRedisAdapter extends the same BaseRedisDriver as the production adapters, implementing its five primitives (doConnect, doDisconnect, executeCommand, createPipeline, createMulti) over an in-memory keyspace. The other ~180 driver methods are inherited unchanged.
Supported commands
| Group | Commands |
|---|---|
| Strings | GET, SET (EX/PX/NX/XX/GET), SETEX, SETNX, APPEND, STRLEN, INCR, INCRBY, DECR, DECRBY, MGET, MSET |
| Keys / TTL | DEL, UNLINK, EXISTS, EXPIRE, PEXPIRE, EXPIREAT, PEXPIREAT, PERSIST, TTL, PTTL, TYPE, KEYS, SCAN |
| Hashes | HSET, HMSET, HGET, HMGET, HGETALL, HDEL, HEXISTS, HLEN, HKEYS, HVALS, HINCRBY |
| Sets | SADD, SREM, SMEMBERS, SISMEMBER, SCARD |
| Sorted sets | ZADD, ZREM, ZCARD, ZSCORE, ZRANGE, ZRANGEBYSCORE, ZREMRANGEBYSCORE, ZCOUNT |
| Lists | LPUSH, RPUSH, LPOP, RPOP, LLEN, LRANGE, LINDEX, LREM |
| Streams | XADD (NOMKSTREAM/MAXLEN/MINID), XLEN, XRANGE, XREVRANGE, XDEL, XTRIM, XINFO STREAM, XGROUP (CREATE/DESTROY/SETID/DELCONSUMER), XREADGROUP, XREAD, XACK, XPENDING, XCLAIM |
| Scripting | EVAL, EVALSHA, SCRIPT LOAD |
| Server | PING, DBSIZE, FLUSHDB, FLUSHALL |
This set covers everything the cache, locks, rate-limit, idempotency, and streams plugins use. TTL is enforced with lazy expiry: an expired key is evicted the next time it is read.
Streams and consumer groups
Streams are modelled with real consumer-group semantics: monotonic <ms>-<seq> ids, per-group delivery cursors, per-consumer pending-entries lists (PEL), delivery counts, and idle-time tracking. So XREADGROUP ... > delivers only never-seen entries and records them in the PEL; XACK clears them; XPENDING and XCLAIM let another consumer reclaim idle messages — exactly what the Streams plugin's consumer loop and auto-claim rely on.
The in-memory driver never truly blocks. A blocking XREADGROUP/XREAD that finds nothing returns null after a short delay (instead of waiting the full BLOCK timeout), which keeps a background consumer poll loop responsive without starving the event loop.
Lua scripting
The plugins rely on Lua scripts for atomicity (e.g. token-bucket rate limiting, owner-checked lock release). The in-memory driver runs them on a small in-house interpreter — no third-party engine. redis.call(...) re-enters the same executor, so a script sees a consistent, single-threaded view, exactly like Redis.
The interpreter supports the bounded subset the project's scripts use:
KEYS/ARGV,localvariables, assignment, and table indexingredis.call/redis.pcall(over the supported command set above)if/elseif/else, numericforloops (with optional step)- arithmetic (
+ - * / %), comparison, logicaland/or/not, concat.. #length,tonumber,tostring,math.floor/ceil/abs/min/max- array + hashmap tables (
HGETALLis flattened to a[field, value, …]array, as on Redis)
Anything outside this subset (e.g. string.*, cjson, while, closures) raises a LuaExecutionError rather than returning a silently wrong result.
Limitations
What it intentionally does not simulate
- No real blocking —
BLOCKonXREADGROUP/XREADreturns promptly instead of waiting; delivery is still correct, only the timing differs. - Pub/Sub is a process-wide bus —
PUBLISH/SUBSCRIBE/PSUBSCRIBEwork across all in-memory connections inside one test process (single-node semantics; no sharded Pub/Sub, no cross-process delivery). - Single keyspace — no multi-database (
SELECT) or cross-slot cluster semantics; behavior matches a single standalone Redis, so a missing hash-tag passes here but can fail on a real cluster. - Not for load testing — it is a correctness tool for unit tests, not a performance simulator.
- Unsupported commands fail loudly — calling a command the driver does not implement throws
MemoryDriverError, surfacing gaps instead of hiding them.
For cluster routing, cross-process Pub/Sub, failover, or performance, test against a real Redis — see When to Use It.
Direct use
You can construct the adapter directly when you need a driver without Nest:
import { MemoryRedisAdapter } from '@nestjs-redisx/testing';
const driver = new MemoryRedisAdapter({ type: 'single', host: 'x', port: 1 });
await driver.connect();
await driver.set('k', 'v');
await driver.get('k'); // 'v'