diff --git a/README.md b/README.md index 629ff8371..db9c91848 100644 --- a/README.md +++ b/README.md @@ -24,8 +24,8 @@ Check the [compatibility table](compat.md) for supported redis commands. ## Usage ([try it in your browser](https://runkit.com/npm/ioredis-mock)) ```js -var Redis = require('ioredis-mock'); -var redis = new Redis({ +const Redis = require('ioredis-mock'); +const redis = new Redis({ // `options.data` does not exist in `ioredis`, only `ioredis-mock` data: { user_next: '3', @@ -40,6 +40,49 @@ var redis = new Redis({ // Basically use it just like ioredis ``` +### Breaking API changes from v5 + +Before v6, each instance of `ioredis-mock` lived in isolation: + +```js +const Redis = require('ioredis-mock'); +const redis1 = new Redis(); +const redis2 = new Redis(); + +await redis1.set('foo', 'bar'); +console.log(await redis1.get('foo'), await redis2.get('foo')); // 'bar', null +``` + +In v6 the [internals were rewritten](/~https://github.com/stipsan/ioredis-mock/pull/1110) to behave more like real life redis, if the host and port is the same, the context is now shared: + +```js +const Redis = require('ioredis-mock'); +const redis1 = new Redis(); +const redis2 = new Redis(); +const redis3 = new Redis({ port: 6380 }); // 6379 is the default port + +await redis1.set('foo', 'bar'); +console.log( + await redis1.get('foo'), // 'bar' + await redis2.get('foo'), // 'bar' + await redis3.get('foo') // null +); +``` + +And since `ioredis-mock` now persist data between instances, you'll [likely](/~https://github.com/luin/ioredis/blob/8278ec0a435756c54ba4f98587aec1a913e8b7d3/test/helpers/global.ts#L8) need to run `flushall` between testing suites: + +```js +const Redis = require('ioredis-mock'); + +afterEach((done) => { + new Redis().flushall().then(() => done()); +}); +``` + +#### `createConnectedClient` is deprecated + +Replace it with `.duplicate()` or use another `new Redis` instance. + ### Configuring Jest Use the jest specific bundle when setting up mocks: @@ -52,24 +95,19 @@ The `ioredis-mock/jest` bundle inlines imports from `ioredis` that `ioredis-mock ### Pub/Sub channels -We also support redis publish/subscribe channels (just like ioredis). -Like ioredis, you need two clients: - -- the pubSub client for subcriptions and events, [which can only be used for subscriptions](https://redis.io/topics/pubsub) -- the usual client for issuing 'synchronous' commands like get, publish, etc +We also support redis [publish/subscribe](https://redis.io/topics/pubsub) channels. +Like [ioredis](/~https://github.com/luin/ioredis#pubsub), you need two clients: ```js -var Redis = require('ioredis-mock'); -var redisPubSub = new Redis(); -// create a second Redis Mock (connected to redisPubSub) -var redisSync = redisPubSub.createConnectedClient(); -redisPubSub.on('message', (channel, message) => { - expect(channel).toBe('emails'); - expect(message).toBe('clark@daily.planet'); - done(); +const Redis = require('ioredis-mock'); +const redisPub = new Redis(); +const redisSub = new Redis(); + +redisSub.on('message', (channel, message) => { + console.log(`Received ${message} from ${channel}`); }); -redisPubSub.subscribe('emails'); -redisSync.publish('emails', 'clark@daily.planet'); +redisSub.subscribe('emails'); +redisPub.publish('emails', 'clark@daily.planet'); ``` ### Promises @@ -93,7 +131,7 @@ You could define a custom command `MULTIPLY` which accepts one key and one argument. A redis key, where you can get the multiplicand, and an argument which will be the multiplicator: ```js -var Redis = require('ioredis-mock'); +const Redis = require('ioredis-mock'); const redis = new Redis({ data: { 'k1': 5 } }); const commandDefinition: { numberOfKeys: 1, lua: 'return KEYS[1] * ARGV[1]' }; redis.defineCommand('MULTIPLY', commandDefinition) // defineCommand(name, definition) @@ -107,7 +145,7 @@ redis.defineCommand('MULTIPLY', commandDefinition) // defineCommand(name, defini You can also achieve the same effect by using the `eval` command: ```js -var Redis = require('ioredis-mock'); +const Redis = require('ioredis-mock'); const redis = new Redis({ data: { k1: 5 } }); const result = redis.eval(`return redis.call("GET", "k1") * 10`); expect(result).toBe(5 * 10); @@ -127,31 +165,16 @@ As a difference from ioredis we currently don't support: Work on Cluster support has started, the current implementation is minimal and PRs welcome #359 ```js -var Redis = require('ioredis-mock'); +const Redis = require('ioredis-mock'); const cluster = new Redis.Cluster(['redis://localhost:7001']); const nodes = cluster.nodes; expect(nodes.length).toEqual(1); ``` -## Roadmap - -This project started off as just an utility in -[another project](/~https://github.com/stipsan/epic) and got open sourced to -benefit the rest of the ioredis community. This means there's work to do before -it's feature complete: - -- [x] Setup testing suite for the library itself. -- [x] Refactor to bluebird promises like ioredis, support node style callback - too. -- [x] Implement remaining basic features that read/write data. -- [x] Implement ioredis - [argument and reply transformers](/~https://github.com/luin/ioredis#transforming-arguments--replies). -- [ ] Connection Events -- [ ] Offline Queue -- [x] Pub/Sub -- [ ] Error Handling -- [ ] Implement [remaining](compat.md) commands +## [Roadmap](/~https://github.com/users/stipsan/projects/1/views/4) + +You can check the [roadmap project page](/~https://github.com/users/stipsan/projects/1/views/4), and [the compat table](compat.md), to see how close we are to feature parity with `ioredis`. ## I need a feature not listed here diff --git a/test/commands/expire.js b/test/commands/expire.js index d4d30dfcb..a202dc421 100644 --- a/test/commands/expire.js +++ b/test/commands/expire.js @@ -75,7 +75,7 @@ describe('expire', () => { it('should emit keyspace notification if configured', (done) => { const redis = new Redis({ notifyKeyspaceEvents: 'gK' }); // gK: generic Keyspace - const redisPubSub = redis.createConnectedClient(); + const redisPubSub = redis.duplicate(); redisPubSub.on('message', (channel, message) => { expect(channel).toBe('__keyspace@0__:foo'); expect(message).toBe('expire'); diff --git a/test/commands/psubscribe.js b/test/commands/psubscribe.js index df7c58025..8a6e4f69d 100644 --- a/test/commands/psubscribe.js +++ b/test/commands/psubscribe.js @@ -46,7 +46,7 @@ describe('psubscribe', () => { it('should allow multiple instances to subscribe to the same channel', () => { const redisOne = new Redis(); - const redisTwo = redisOne.createConnectedClient(); + const redisTwo = new Redis(); return Promise.all([ redisOne.psubscribe('first.*', 'second.*'), @@ -66,7 +66,7 @@ describe('psubscribe', () => { redisOne.on('pmessage', promiseOneFulfill); redisTwo.on('pmessage', PromiseTwoFulfill); - redisOne.createConnectedClient().publish('first.test', 'blah'); + redisOne.duplicate().publish('first.test', 'blah'); return Promise.all([promiseOne, promiseTwo]); }); diff --git a/test/commands/publish.js b/test/commands/publish.js index 9015aa870..19e1fac96 100644 --- a/test/commands/publish.js +++ b/test/commands/publish.js @@ -10,7 +10,7 @@ describe('publish', () => { it('should return 1 when publishing with a single subscriber', () => { const redisPubSub = new Redis(); - const redis2 = redisPubSub.createConnectedClient(); + const redis2 = new Redis(); redisPubSub.subscribe('emails'); return redis2 .publish('emails', 'clark@daily.planet') @@ -19,7 +19,7 @@ describe('publish', () => { it('should publish a message, which can be received by a previous subscribe', (done) => { const redisPubSub = new Redis(); - const redis2 = redisPubSub.createConnectedClient(); + const redis2 = new Redis(); redisPubSub.on('message', (channel, message) => { expect(channel).toBe('emails'); expect(message).toBe('clark@daily.planet'); @@ -31,7 +31,7 @@ describe('publish', () => { it('should emit messageBuffer event when a Buffer message is published on a subscribed channel', (done) => { const redisPubSub = new Redis(); - const redis2 = redisPubSub.createConnectedClient(); + const redis2 = new Redis(); const buffer = Buffer.alloc(8); redisPubSub.on('messageBuffer', (channel, message) => { expect(channel).toBe('emails'); @@ -44,7 +44,7 @@ describe('publish', () => { it('should return 1 when publishing with a single pattern subscriber', () => { const redisPubSub = new Redis(); - const redis2 = redisPubSub.createConnectedClient(); + const redis2 = new Redis(); redisPubSub.psubscribe('emails.*'); return redis2 .publish('emails.urgent', 'clark@daily.planet') @@ -53,7 +53,7 @@ describe('publish', () => { it('should publish a message, which can be received by a previous psubscribe', (done) => { const redisPubSub = new Redis(); - const redis2 = redisPubSub.createConnectedClient(); + const redis2 = new Redis(); redisPubSub.on('pmessage', (pattern, channel, message) => { expect(pattern).toBe('emails.*'); expect(channel).toBe('emails.urgent'); @@ -66,7 +66,7 @@ describe('publish', () => { it('should emit a pmessageBuffer event when a Buffer message is published matching a psubscribed pattern', (done) => { const redisPubSub = new Redis(); - const redis2 = redisPubSub.createConnectedClient(); + const redis2 = new Redis(); const buffer = Buffer.alloc(0); redisPubSub.on('pmessageBuffer', (pattern, channel, message) => { expect(pattern).toBe('emails.*'); diff --git a/test/commands/punsubscribe.js b/test/commands/punsubscribe.js index 2aaffdff2..ed215ecb2 100644 --- a/test/commands/punsubscribe.js +++ b/test/commands/punsubscribe.js @@ -39,7 +39,7 @@ describe('punsubscribe', () => { it('should unsubscribe only one instance when more than one is subscribed to a channel', () => { const redisOne = new Redis(); - const redisTwo = redisOne.createConnectedClient(); + const redisTwo = new Redis(); return Promise.all([ redisOne.psubscribe('first.*'), @@ -56,7 +56,7 @@ describe('punsubscribe', () => { redisOne.on('pmessage', promiseFulfill); - redisOne.createConnectedClient().publish('first.test', 'TEST'); + redisOne.duplicate().publish('first.test', 'TEST'); return promise; }); diff --git a/test/commands/rename.js b/test/commands/rename.js index cb9b30f17..7f5e1702a 100644 --- a/test/commands/rename.js +++ b/test/commands/rename.js @@ -18,7 +18,7 @@ describe('rename', () => { it('should emit keyspace notifications if configured', (done) => { const redis = new Redis({ notifyKeyspaceEvents: 'gK' }); // gK: generic Keyspace - const redisPubSub = redis.createConnectedClient(); + const redisPubSub = redis.duplicate(); let messagesReceived = 0; redisPubSub.on('message', (channel, message) => { messagesReceived++; diff --git a/test/commands/subscribe.js b/test/commands/subscribe.js index 07060992c..5a048202e 100644 --- a/test/commands/subscribe.js +++ b/test/commands/subscribe.js @@ -51,7 +51,7 @@ describe('subscribe', () => { it('should allow multiple instances to subscribe to the same channel', () => { const redisOne = new Redis(); - const redisTwo = redisOne.createConnectedClient(); + const redisTwo = new Redis(); return Promise.all([ redisOne.subscribe('first', 'second'), @@ -71,7 +71,7 @@ describe('subscribe', () => { redisOne.on('message', promiseOneFulfill); redisTwo.on('message', PromiseTwoFulfill); - redisOne.createConnectedClient().publish('first', 'blah'); + redisOne.duplicate().publish('first', 'blah'); return Promise.all([promiseOne, promiseTwo]); }); diff --git a/test/commands/unsubscribe.js b/test/commands/unsubscribe.js index 40993c3fa..f6a5a4f50 100644 --- a/test/commands/unsubscribe.js +++ b/test/commands/unsubscribe.js @@ -38,7 +38,7 @@ describe('unsubscribe', () => { it('should unsubscribe only one instance when more than one is subscribed to a channel', () => { const redisOne = new Redis(); - const redisTwo = redisOne.createConnectedClient(); + const redisTwo = new Redis(); return Promise.all([ redisOne.subscribe('first'), @@ -55,7 +55,7 @@ describe('unsubscribe', () => { redisOne.on('message', promiseFulfill); - redisOne.createConnectedClient().publish('first', 'TEST'); + redisOne.duplicate().publish('first', 'TEST'); return promise; }); @@ -63,7 +63,7 @@ describe('unsubscribe', () => { it('should not alter parent instance when connected client unsubscribes', () => { const redisOne = new Redis(); - const redisTwo = redisOne.createConnectedClient(); + const redisTwo = new Redis(); return redisOne .subscribe('first') .then(() => redisTwo.unsubscribe('first')) diff --git a/test/keyspace-notifications.js b/test/keyspace-notifications.js index 236a0771a..318a945cf 100644 --- a/test/keyspace-notifications.js +++ b/test/keyspace-notifications.js @@ -67,7 +67,7 @@ describe('parseKeyspaceEvents', () => { describe('keyspaceNotifications', () => { it('should appear when configured and the triggering event occurs', (done) => { const redis = new Redis({ notifyKeyspaceEvents: 'gK' }); // gK: generic keyspace - const redisPubSub = redis.createConnectedClient(); + const redisPubSub = redis.duplicate(); redisPubSub.on('message', (channel, message) => { expect(channel).toBe('__keyspace@0__:key'); expect(message).toBe('del'); @@ -105,7 +105,7 @@ describe('keyspaceNotifications', () => { describe('keyeventNotifications', () => { it('should appear when configured and the triggering event occurs', (done) => { const redis = new Redis({ notifyKeyspaceEvents: 'gE' }); // gK: generic keyevent - const redisPubSub = redis.createConnectedClient(); + const redisPubSub = redis.duplicate(); redisPubSub.on('message', (channel, message) => { expect(channel).toBe('__keyevent@0__:del'); expect(message).toBe('key'); diff --git a/test/multiple-mocks.js b/test/multiple-mocks.js index 940612c44..0245ba82f 100644 --- a/test/multiple-mocks.js +++ b/test/multiple-mocks.js @@ -3,7 +3,7 @@ import Redis from 'ioredis'; describe('multipleMocks', () => { it('should be possible to create a second IORedis client, which is working on shared data with the first client', () => { const client1 = new Redis(); - const client2 = client1.createConnectedClient(); + const client2 = new Redis(); client1.hset('testing', 'test', '2').then(() => { client2.hget('testing', 'test').then((val) => expect(val).toBe('2')); }); @@ -11,7 +11,7 @@ describe('multipleMocks', () => { it('should be possible to create a second IORedis client, which is working on shared channels with the first client', (done) => { const client1 = new Redis(); - const client2 = client1.createConnectedClient(); + const client2 = new Redis(); client1.on('message', (channel, message) => { expect(channel).toBe('channel'); expect(message).toBe('hello'); @@ -31,7 +31,7 @@ describe('multipleMocks', () => { const connectedClients = []; for (let i = 0; i < 10; i++) { - const connectedClient = client.createConnectedClient(); + const connectedClient = client.duplicate(); connectedClients.push(connectedClient); connectedClient.subscribe(testChannel); }