1 |
|
2 |
|
3 | # fakeredis - a fake redis for node.js
|
4 |
|
5 |
|
6 | This module provides easy-to-use simulated instances of Redis
|
7 | to which you appear to be connected via the
|
8 | [redis](https://github.com/mranney/node_redis) client by [Matt Ranney](https://github.com/mranney).
|
9 | **It helps with writing tests** in two ways:
|
10 | your tests won't require an actual redis instance
|
11 | and you'll be able to safely run as many tests in parallel as you want.
|
12 |
|
13 | [![NPM Version](https://nodei.co/npm/fakeredis.png?downloads=true)](https://npmjs.org/package/fakeredis)
|
14 | [![Build Status](https://secure.travis-ci.org/hdachev/fakeredis.png?branch=master)](http://travis-ci.org/hdachev/fakeredis)
|
15 |
|
16 |
|
17 | ## Usage
|
18 |
|
19 | Install:
|
20 |
|
21 | npm install fakeredis
|
22 |
|
23 | You can use fakeredis as you would use node_redis,
|
24 | just changing the module name from `redis` to `fakeredis`:
|
25 |
|
26 | ```javascript
|
27 | var client = require("fakeredis").createClient(port, host);
|
28 | ```
|
29 |
|
30 | Both parameters are optional,
|
31 | and only serve to determine if you want to reuse a an existing fakeredis instance or not.
|
32 | You can also just name your backends arbitrarily:
|
33 |
|
34 | ```javascript
|
35 |
|
36 | // Create a connection to a fresh fakeredis instance:
|
37 | var client = fakeredis.createClient("social stuff");
|
38 |
|
39 | // Connect to the same backend via another simulated connection:
|
40 | var concurrentClient = fakeredis.createClient("social stuff");
|
41 | ```
|
42 |
|
43 | By omitting both parameters,
|
44 | you simply create a new blank slate fakeredis instance:
|
45 |
|
46 | ```javascript
|
47 | var client = require("fakeredis").createClient();
|
48 | ```
|
49 |
|
50 |
|
51 | In other words,
|
52 | every time you create a client specifying the same port and/or name
|
53 | you reuse the same simulated backend.
|
54 | This makes most sense when you need a concurrent client setup for some test,
|
55 | say because you need to publish / subscribe,
|
56 | or because you want to test something that's based on `MULTI`/`EXEC`
|
57 | and uses optimistic locking with `WATCH`/`UNWATCH`.
|
58 |
|
59 | In any case, fakeredis is great for testing
|
60 | because you can run as many tests in parallel as you wish,
|
61 | and that's also why you'll generally be naming your clients
|
62 | in a way that ensures tests don't collide.
|
63 |
|
64 |
|
65 |
|
66 | ## Intended differences from a true Redis
|
67 |
|
68 | One key difference is that the output of some commands,
|
69 | such as `SMEMBERS`, `HKEYS`, `HVALS`,
|
70 | comes out sorted lexicographically to provide for simpler testing.
|
71 | This means that some tests that make use of undocumented Redis behaviours
|
72 | such as the chronological order of retrieval for members in a set
|
73 | may fail when attempted with fakeredis.
|
74 | To solve this,
|
75 | whenever there is no documented sort order for a given Redis command's multi-bulk reply,
|
76 | sort the output before asserting equality to ensure your tests run everywhere.
|
77 |
|
78 | Another major difference is that commands that accept modifier parameters, such as
|
79 | `SORT key [BY pattern] [LIMIT offset count] [GET pattern [GET pattern ...]] [ASC|DESC] [ALPHA] [STORE destination]`
|
80 | currently only accept these parameters in the order that is stated in the documentation.
|
81 | For example,
|
82 | in Redis it appears to be perfectly legitimate to have `SORT myset ALPHA LIMIT 0 5`,
|
83 | but in fakeredis this will currently return a syntax error.
|
84 |
|
85 | I'm totally open to discussion on both points.
|
86 |
|
87 |
|
88 | ### Implemented subset:
|
89 |
|
90 | All string, list, hash, set and sorted set commands,
|
91 | most keyspace commands, and some connection and server commands.
|
92 | Pubsub, transactions with optimistic locking are also fully implemented.
|
93 |
|
94 | List of **available** commands:
|
95 |
|
96 | Keyspace:
|
97 |
|
98 | DBSIZE
|
99 | EXISTS
|
100 | EXPIRE
|
101 | EXPIREAT
|
102 | FLUSHDB
|
103 | KEYS
|
104 | PERSIST
|
105 | DEL
|
106 | RANDOMKEY
|
107 | RENAME
|
108 | RENAMENX
|
109 | SORT
|
110 | TTL
|
111 | TYPE
|
112 |
|
113 | Strings:
|
114 |
|
115 | APPEND
|
116 | DECR
|
117 | DECRBY
|
118 | GET
|
119 | GETBIT
|
120 | GETRANGE
|
121 | GETSET
|
122 | INCR
|
123 | INCRBY
|
124 | MGET
|
125 | MSET
|
126 | MSETNX
|
127 | SET
|
128 | SETBIT
|
129 | SETEX
|
130 | SETNX
|
131 | SETRANGE
|
132 |
|
133 | Hashes:
|
134 |
|
135 | HDEL
|
136 | HEXISTS
|
137 | HGET
|
138 | HGETALL
|
139 | HINCRBY
|
140 | HKEYS
|
141 | HLEN
|
142 | HMGET
|
143 | HMSET
|
144 | HSET
|
145 | HSETNX
|
146 | HVALS
|
147 |
|
148 | Lists:
|
149 |
|
150 | BLPOP
|
151 | BRPOP
|
152 | BRPOPLPUSH
|
153 | LINDEX
|
154 | LINSERT
|
155 | LLEN
|
156 | LPOP
|
157 | LPUSH
|
158 | LPUSHX
|
159 | LRANGE
|
160 | LREM
|
161 | LSET
|
162 | LTRIM
|
163 | RPOP
|
164 | RPOPLPUSH
|
165 | RPUSH
|
166 | RPUSHX
|
167 |
|
168 | Sets:
|
169 |
|
170 | SADD
|
171 | SCARD
|
172 | SDIFF
|
173 | SDIFFSTORE
|
174 | SINTER
|
175 | SINTERSTORE
|
176 | SISMEMBER
|
177 | SMEMBERS
|
178 | SMOVE
|
179 | SPOP
|
180 | SRANDMEMBER
|
181 | SREM
|
182 | STRLEN
|
183 | SUNION
|
184 | SUNIONSTORE
|
185 |
|
186 | Sorted Sets:
|
187 |
|
188 | ZADD
|
189 | ZCARD
|
190 | ZCOUNT
|
191 | ZINCRBY
|
192 | ZINTERSTORE
|
193 | ZRANGE
|
194 | ZRANGEBYSCORE
|
195 | ZRANK
|
196 | ZREM
|
197 | ZREMRANGEBYRANK
|
198 | ZREMRANGEBYSCORE
|
199 | ZREVRANGE
|
200 | ZREVRANGEBYSCORE
|
201 | ZREVRANK
|
202 | ZSCORE
|
203 | ZUNIONSTORE
|
204 |
|
205 | Pub/Sub:
|
206 |
|
207 | PSUBSCRIBE
|
208 | PUBLISH
|
209 | PUNSUBSCRIBE
|
210 | SUBSCRIBE
|
211 | UNSUBSCRIBE
|
212 |
|
213 | Transactions:
|
214 |
|
215 | DISCARD
|
216 | EXEC
|
217 | MULTI
|
218 | UNWATCH
|
219 | WATCH
|
220 |
|
221 | Connection and Server:
|
222 |
|
223 | ECHO
|
224 | PING
|
225 | QUIT
|
226 | SELECT
|
227 |
|
228 | These do nothing but return `OK`:
|
229 |
|
230 | AUTH
|
231 | BGREWRITEAOF
|
232 | BGSAVE
|
233 | SAVE
|
234 |
|
235 |
|
236 | ### What's missing:
|
237 |
|
238 | Most notably, `MONITOR` is still missing.
|
239 |
|
240 | Also note that **none of the node_redis client constructor options are available**,
|
241 | which means no `detect_buffers` and `return_buffers`.
|
242 | Command arguments are always stringified at the fake connection level,
|
243 | and replies are always returned as `null`, `String`, `Number` or `Array`.
|
244 |
|
245 | Finally,
|
246 | none of the `ready`, `connect`, `error`, `end`, `drain` and `idle`
|
247 | client events are currently implemented.
|
248 |
|
249 | List of **missing** commands (will throw upon attempt to use):
|
250 |
|
251 | Connection and Server:
|
252 |
|
253 | CONFIG GET
|
254 | CONFIG SET
|
255 | CONFIG RESETSTAT
|
256 | DEBUG OBJECT
|
257 | DEBUG SEGFAULT
|
258 | FLUSHALL
|
259 | INFO
|
260 | LASTSAVE
|
261 | MONITOR
|
262 | MOVE
|
263 | OBJECT
|
264 | SHUTDOWN
|
265 | SLAVEOF
|
266 | SYNC
|
267 |
|
268 |
|
269 |
|
270 | ## Helpers
|
271 |
|
272 | To facilitate development and testing,
|
273 | fakeredis provides some additional methods on the client object.
|
274 |
|
275 |
|
276 | ### Prettyprinting:
|
277 |
|
278 | ```javascript
|
279 | fakeredisClient.pretty();
|
280 | fakeredisClient.pretty("p*tte?n");
|
281 | fakeredisClient.pretty(options);
|
282 | ```
|
283 |
|
284 | `.pretty()` will prettyprint to stdout the entire keyspace
|
285 | or a subset of keys specificed with a redis pattern
|
286 | of the same kind that's used for `KEYS` and `PSUBSCRIBE`.
|
287 | Keep in mind .pretty() is async,
|
288 | because it works as a normal client command
|
289 | and hence needs to respect the command order,
|
290 | fake pipelining and latency and all,
|
291 | so that you can do stuff like:
|
292 |
|
293 | ```javascript
|
294 | var client = require("fakeredis").createClient();
|
295 |
|
296 | client.SADD('hello', 'world', 'Jenny', 'Sam');
|
297 | client.LPUSH('mylist', 'hey', 'ho', 'letsgo');
|
298 | client.pretty({label: "my stuff", pattern: "*"});
|
299 | ```
|
300 |
|
301 | Which would print *(in color!)*
|
302 |
|
303 | my stuff:
|
304 |
|
305 | set hello
|
306 | -1 Jenny, Sam, world
|
307 |
|
308 | list mylist
|
309 | -1 letsgo, ho, hey
|
310 |
|
311 |
|
312 | ### Keyspace dumps:
|
313 |
|
314 | ```javascript
|
315 | fakeredisClient.getKeypsace(callback);
|
316 | fakeredisClient.getKeypsace("p*tte?n", callback);
|
317 | fakeredisClient.getKeyspace(options, callback);
|
318 | ```
|
319 |
|
320 | Will `callback(err, data)` with an array
|
321 | that enumerates the whole keyspace,
|
322 | or the requested subset, in the following manner:
|
323 |
|
324 | ```javascript
|
325 | [ key1, ttl1, type1, value1
|
326 | , key2, ttl2, type2, value2
|
327 | , ... ]
|
328 | ```
|
329 |
|
330 | The keyspace is sorted lexicographically by key,
|
331 | string values are strings,
|
332 | list values are the output of `LRANGE 0 -1`,
|
333 | hashes come out as the output of `HGETALL` for hashes
|
334 | (no syntactic sugar though, so an Array of `[field, value, field, value, ...]`),
|
335 | `SMEMBERS` output is used for sets,
|
336 | and `ZRANGE 0 -1 WITHSCORES` for sorted sets,
|
337 | each of which is sorted lexicographically in a way that makes sense,
|
338 | so that the final result is simple enough to assert deep equality against.
|
339 |
|
340 | In any case, you'll probably need to reformat these keyspace dumps
|
341 | to a format that makes more sense for your testing needs.
|
342 | There are a couple of transforms that are included out of the box:
|
343 |
|
344 | ```javascript
|
345 | fakeredisClient.getKeypsace({pattern: "myz*", map: true}, callback);
|
346 | ```
|
347 |
|
348 | If you only care about the key and value of each entry,
|
349 | you can set the **map** option to a truthy value,
|
350 | you will instead receive the keyspace dump as a key-value map of the kind:
|
351 |
|
352 | ```javascript
|
353 | { key1: value1, key2: value2, ... }
|
354 | ```
|
355 |
|
356 | This means you're skipping ttl and key type info though. You can also do:
|
357 |
|
358 | ```javascript
|
359 | fakeredisClient.getKeypsace({pattern: "myz*", group: true}, callback);
|
360 | ```
|
361 |
|
362 | Which will return an `Array` of `Array`s,
|
363 | one for each keyspace entry, so that you end up with:
|
364 |
|
365 | ```javascript
|
366 | [ [ key1, ttl1, type1, value1 ]
|
367 | , [ key2, ttl2, type2, value2 ]
|
368 | , ... ]
|
369 | ```
|
370 |
|
371 | The benefit of this option is that you can sort the outer array as you like more easily.
|
372 |
|
373 |
|
374 |
|
375 | ## Similar projects
|
376 |
|
377 | You might also want to check out these similar implementations in
|
378 | [python](https://github.com/jamesls/fakeredis) and
|
379 | [ruby](https://github.com/guilleiguaran/fakeredis).
|
380 |
|
381 |
|
382 |
|
383 | ## MIT License
|
384 |
|
385 | Copyright (c) 2012 Hristo Dachev
|
386 |
|
387 | Permission is hereby granted, free of charge, to any person
|
388 | obtaining a copy of this software and associated documentation
|
389 | files (the "Software"), to deal in the Software without
|
390 | restriction, including without limitation the rights to use,
|
391 | copy, modify, merge, publish, distribute, sublicense, and/or sell
|
392 | copies of the Software, and to permit persons to whom the
|
393 | Software is furnished to do so, subject to the following
|
394 | conditions:
|
395 |
|
396 | The above copyright notice and this permission notice shall be
|
397 | included in all copies or substantial portions of the Software.
|
398 |
|
399 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
400 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
401 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
402 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
403 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
404 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
405 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
406 | OTHER DEALINGS IN THE SOFTWARE.
|
407 |
|