Giter VIP home page Giter VIP logo

redismock's Introduction

Redis PHP Mock Build Status Total Downloads

PHP 7.1 library providing a Redis PHP mock for your tests.

Work for now only with predis

Installation

$ composer require --dev m6web/redis-mock

Functions

It currently mocks these Redis commands :

Redis command Description
DBSIZE Returns the number of keys in the selected database
DEL key [key ...] Deletes one or more keys
DECR key Decrements the integer value of a key by one
DECRBY key decrement Decrements the integer value of a key by decrement value
DECRBYFLOAT key decrement Decrements the float value of a key by decrement value
EXISTS key Determines if a key exists
EXPIRE key seconds Sets a key's time to live in seconds
FLUSHDB Flushes the database
GET key Gets the value of a key
HDEL key array<fields> Delete hash fields
HEXISTS key field Determines if a hash field exists
HMGET key array<field> Gets the values of multiple hash fields
HGET key field Gets the value of a hash field
HGETALL key Gets all the fields and values in a hash
HKEYS key Gets all the fields in a hash
HLEN key Gets the number of fields in a hash
HMSET key array<field, value> Sets each value in the corresponding field
HSET key field value Sets the string value of a hash field
HSETNX key field value Sets field in the hash stored at key to value, only if field does not yet exist
HINCRBY key field increment Increments the integer stored at field in the hash stored at key by increment.
INCR key Increments the integer value of a key by one
INCRBY key increment Increments the integer value of a key by increment value
INCRBYFLOAT key increment Increments the float value of a key by increment value
KEYS pattern Finds all keys matching the given pattern
LINDEX key index Returns the element at index index in the list stored at key
LLEN key Returns the length of the list stored at key
LPUSH key value [value ...] Pushs values at the head of a list
LPOP key Pops values at the head of a list
LREM key value count Removes count instances of value from the head of a list (follows the predis parameters order)
LTRIM key start stop Removes the values of the key list which are outside the range start...stop
LRANGE key start stop Gets a range of elements from a list
MGET array<field> Gets the values of multiple keys
MSET array<field, value> Sets the string values of multiple keys
QUIT Quit the REDIS
RPUSH key value Pushs values at the tail of a list
RPOP key Pops values at the tail of a list
SCAN Iterates the set of keys in the currently selected Redis database.
SSCAN Iterates elements of Sets types.
SET key value Sets the string value of a key
SETEX key seconds value Sets the value and expiration of a key
SETNX key value Sets key to hold value if key does not exist
SADD key member [member ...] Adds one or more members to a set
SDIFF key key [key ...] Returns the members of the set resulting from the difference between the first set and all the successive sets.
SISMEMBER key member Determines if a member is in a set
SMEMBERS key Gets all the members in a set
SUNION key [key ...] Returns the members of the set resulting from the union of all the given sets.
SINTER key [key ...] Returns the members of the set resulting from the intersection of all the given sets.
SCARD key Get cardinality of set (count of members)
SREM key member [member ...] Removes one or more members from a set
TTL key  Gets the time to live for a key
TYPE key Returns the string representation of the type of the value stored at key.
ZADD key score member Adds one member to a sorted set, or update its score if it already exists
ZINCRBY key increment member Increments the score of member in the sorted set stored at key by increment
ZCARD key Returns the sorted set cardinality (number of elements) of the sorted set stored at key
ZCOUNT key min max Returns the number of elements in the stored set at key with a score between min and max.
ZRANGE key start stop [withscores] Returns the specified range of members in a sorted set
ZRANGEBYSCORE key min max options Returns a range of members in a sorted set, by score
ZRANK key member Returns the rank of member in the sorted set stored at key, with the scores ordered from low to high
ZREVRANK key member Returns the rank of member in the sorted set stored at key, with the scores ordered from high to low
ZREM key member Removes one membner from a sorted set
ZREMRANGEBYSCORE key min max Removes all members in a sorted set within the given scores
ZREVRANGE key start stop [withscores] Returns the specified range of members in a sorted set, with scores ordered from high to low
ZREVRANGEBYSCORE key min max options Returns a range of members in a sorted set, by score, with scores ordered from high to low
ZSCAN Iterates elements of Sorted Sets types.
ZSCORE key member Returns the score of member in the sorted set at key
ZUNIONSTORE dest numkeys key ... [weights ...] [aggregate SUM/MIN/MAX] Computes the union of the stored sets given by the specified keys, store the result in the destination key, and returns the number of elements of the new sorted set.

It mocks MULTI, DISCARD and EXEC commands but without any transaction behaviors, they just make the interface fluent and return each command results. PIPELINE and EXECUTE pseudo commands (client pipelining) are also mocked. EVAL, EVALSHA, WATCH and UNWATCH are just stubs—they won't execute anything

Usage

RedisMock library provides a factory able to build a mocked class of your Redis library that can be directly injected in your application :

$factory          = new \M6Web\Component\RedisMock\RedisMockFactory();
$myRedisMockClass = $factory->getAdapterClass('My\Redis\Library');
$myRedisMock      = new $myRedisMockClass($myParameters);

In a simpler way, if you don't need to instanciate the mocked class with custom parameters (e.g. to easier inject the mock using Symfony config file), you can use getAdapter instead of getAdapterClass to directly create the adapter :

$factory     = new \M6Web\Component\RedisMock\RedisMockFactory();
$myRedisMock = $factory->getAdapter('My\Redis\Library');

WARNING !

  • RedisMock doesn't implement all Redis features and commands. The mock can have undesired behavior if your parent class uses unsupported features.
  • Storage is static and therefore shared by all instances.

Note : the factory will throw an exception by default if your parent class implements unsupported commands. If you want even so partially use the mock, you can specify the second parameter when you build it $factory->getAdapter('My\Redis\Library', true). The exception will then thrown only when the command is called.

Static storage & Multiple servers

The storage in the RedisMock class is organized by named areas. The default area's name is the empty string '' but you can specify an alternate area name when calling the factory's getAdapter method.

getAdapter($classToExtend, $failOnlyAtRuntime = false, $ignoreConstructor = true, $storage = '')

This enables the mocking of several remote Redis servers, each one with its own storage area.

However, one same area remains statically shared across all the instances bound to it.

Tests

The development environment is provided by Vagrant and the Xotelia box.

$ cp Vagrantfile.dist Vagrantfile
$ vagrant up
$ vagrant ssh
$ cd /vagrant
$ composer install
$ ./vendor/bin/atoum

Credits

Developped by the Cytron Team of M6 Web.
Tested with atoum.

License

RedisMock is licensed under the MIT license.

redismock's People

Contributors

aburakovskiy avatar alex-scott avatar asilgalis avatar charrisbc avatar codepluswander avatar dchancogne avatar docroms avatar fabdsp avatar fdubost avatar gm-ghanover avatar kjnsn avatar kuikui avatar lnahiro avatar magnetik avatar mikaelrandy avatar notfloran avatar oliboy50 avatar omansour avatar pincheng0101 avatar programming-services avatar rajivraman avatar robertmarney avatar sevrugin avatar syffer avatar thirsch avatar tomlankhorst avatar vdechenaux avatar zzgab avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

redismock's Issues

HDEL - wrong arguments

array_key_exists(): The first argument should be either a string or an integer at
m6web/redis-mock/src/M6Web/Component/RedisMock/RedisMock.php:756

vendor/predis/predis/src/ClientInterface.php:69

  • @method int hdel($key, array $fields)

Mock several servers

Currently the client mocks a connection to one single Redis server, and storage is static, shared across all instances of the mock (which is good).
I would like to use a test scenario involving two (or more) Redis servers. The storage should remain mock-static, but per "Redis server".

Shall I fork and propose?

Pipeline not working?

So I'm using this project in a Lumen setup that uses Predis 1.1. Mocking a Redis server works perfectly when using individual Redis commands, however, I can't get it to work when using pipeline. When doing something like this:

$response = Redis::pipeline(function($pipe) {
   // Run redis commands here
});

I'm expecting that $response will be an array with command results. However, when mocking the Predis Client, I get an instance of RedisMock instead.

Is the pipeline support not fully implemented or am I missing something here?

Thanks.

ZADD: allowing multiple member-score pairs

Hi

Right now I'm using Predis as redis library. The ZADD function is defined this way:
zadd($key, array $membersAndScoresDictionary)

When using RedisMock in my unit tests, I get an error stating that argument 3 is missing for zadd. (as ZADD is defined as zadd($key, $score, $member)).

Does it seem logical to add this change? Or do other PHP Redis library implment zadd differently?

wishlist: blpop

Hi, thanks for a great lib!

I would love to see support for llen (should be easy) and blpop (a bit harder I think). Blpop could work by just adding a warning if the queue is emtpy and retuning after a sleep() timeout.

wrongtype exception missing

redis> sadd raoul toto
(integer) 1
redis> hdel raoul toto
WRONGTYPE Operation against a key holding the wrong kind of value
redis>

actually, hard to mock cause a concrete class can throw any kind of exception ...

Predis transactions not supported?

Hi, I thought Predis transactions were supported - is that not correct?

Here's the code I was trying to test:

function fetchMessages($redisClient) {
        $messages = $redisClient->zrevrangebyscore('myqueue', time(), 0);
        $queueId = 'myqueue';

        foreach ($messages as $message) {
            $dmes = json_decode($message, true);
            $redisClient->transaction(
                function ($tx) use ($dmes, $queueId, $message) {
                    $tx->zrem($queueId, $message);
                    $tx->rpush($dmes['destination'], json_encode($dmes['message']));
                }
            );
        }

}

And the test:

function testMyRedisQueue() {
$factory = new \M6Web\Component\RedisMock\RedisMockFactory();
        $redis = $factory->getAdapter('Predis\Client', true, false, '', [
           [ 'profile' => '3.0']
        ]);

  $message = json_encode(['destination'=>'tq1', 'message' => ['key' => 'v'], 'timestamp' =>  new \DateTimeImmutable('now - 3 hours')->getTimestamp()]);
        $redis->zadd('myqueue',  \DateTimeImmutable('now - 3 hours')->getTimestamp(), $message);

        $this->assertEquals(0, $redis->llen('tq1'));

       fetchMessages($redis); 
  
       $this->assertEquals(1, $this->redis->llen('tq1'));

}

I have isolated the problem to the code within the transaction call. If I run it outside the transaction then the test works. Any ideas on how to fix this?

Warning "array_or_countable" in mehtod RedisMock:del() on PHP 7.2

  • Version: 2.8.0
  • PHP: 7.2
  • Warning: "Parameter must be an array or an object that implements Countable"

Affected code:

$deletedKeyCount += count(self::$dataValues[$this->storage][$k]);

Bugfix:

$value = self::$dataValues[$this->storage][$k];
$deletedKeyCount += is_array($value) ? count($value) : 1;

Not working with phpredis

We discovered an issue with phpredis.

We wan't to create a Redis mock to inject them into another class that expects an instance of Redis.
Our solution now is to not use the factory but creating RedisMock directly, remove the typehint in our sut and inject the RedisMock directly.

System Informations

OS: Linux
PHP: PHP 7.1.10-1+0~20170929170631.9+jessie~1.gbp501135 (cli)
phpredis: 3.1.2

Reproduce

$factory          = new \M6Web\Component\RedisMock\RedisMockFactory();
$myRedisMockClass = $factory->getAdapter(Redis::class, true);
$myRedisMock      = new $myRedisMockClass();

$myRedisMock->hSetNx('foo', 'bar', 'baz');

Error

ArgumentCountError: Too few arguments to function M6Web\Component\RedisMock\RedisMock::hsetnx(), 0 passed in vendor/m6web/redis-mock/src/M6Web/Component/RedisMock/RedisMockFactory.php(226) : eval()'d code on line 370 and exactly 3 expected

vendor/m6web/redis-mock/src/M6Web/Component/RedisMock/RedisMock.php:579

More informations

we debugged a bit and found out that the reflection of Redis give no details about the parameters for most of the methods.

php > $ref = new ReflectionClass(Redis::class);
php > echo (string)$ref;
Class [ <internal:redis> class Redis ] {
...
    Method [ <internal:redis> public method hGet ] {
    }

    Method [ <internal:redis> public method hSet ] {
    }

    Method [ <internal:redis> public method hSetNx ] {
    }

    Method [ <internal:redis> public method hDel ] {
    }
...
    Method [ <internal:redis> public method command ] {
    }

    Method [ <internal:redis> public method scan ] {

      - Parameters [3] {
        Parameter #0 [ <required> &$i_iterator ]
        Parameter #1 [ <optional> $str_pattern ]
        Parameter #2 [ <optional> $i_count ]
      }
    }

    Method [ <internal:redis> public method hscan ] {

      - Parameters [4] {
        Parameter #0 [ <required> $str_key ]
        Parameter #1 [ <required> &$i_iterator ]
        Parameter #2 [ <optional> $str_pattern ]
        Parameter #3 [ <optional> $i_count ]
      }
    }
...
}

RedisMock set method expire time value is in seconds

The set method signature on RedisMock seems to be wrong and store ttl in second.

However set should support milliseconds for ttl definition.

Proposal
public function set($key, $value, $options = null, $ttl = null)
{
    /**
     * Per https://redis.io/commands/set#options
     * EX seconds -- Set the specified expire time, in seconds.
     * PX milliseconds -- Set the specified expire time, in milliseconds.
     * NX -- Only set the key if it does not already exist.
     * XX -- Only set the key if it already exist.
     */
    if (!is_null($options) && !in_array($options, ['ex', 'px', 'nx', 'xx'])) {
        $this->returnPipedInfo('(error) ERR syntax error');
    }
    if ('nx'=== $options && $this->get($key)) {
        return $this->returnPipedInfo(0);
    }
    if ('xx' === $options && !$this->get($key)) {
        return $this->returnPipedInfo(0);
    }

    if ($options === 'ex') {
        $ttl = $ttl;
    } elseif ($options === 'px') {
        $ttl = $ttl / 1000;
    }

    self::$dataValues[$this->storage][$key] = $value;
    self::$dataTypes[$this->storage][$key] = 'string';

    if (!is_null($ttl)) {
        self::$dataTtl[$this->storage][$key] = microtime(true) + $ttl;
    }

As it will be impacted, We should update RedisSessionHandler, and check that ttl resolution is compatible with symfony session resolution

Add support for predis v2

Updating predis to ~v2 in composer.json fails the tests with:

PHP Fatal error:  Declaration of M6Web\Component\RedisMock\RedisMock_Predis_Client_Adapter::pipeline($arguments) must be compatible with Predis\Client::pipeline(...$arguments) in /var/www/html/scriptsender/vendor/m6web/redis-mock/src/M6Web/Component/RedisMock/RedisMockFactory.php(226) : eval()'d code on line 40

Issue when mocking Predis

Trying to mock Predis (https://github.com/nrk/predis). The following exception is thrown by the getAdapterClass() function:

$factory          = new \M6Web\Component\RedisMock\RedisMockFactory();
$myRedisMockClass = $factory->getAdapterClass('Predis\Client', true);
M6Web\Component\RedisMock\UnsupportedException: Redis command `quit` is not supported by RedisMock.

.../m6web/redis-mock/src/M6Web/Component/RedisMock/RedisMockFactory.php:267
.../m6web/redis-mock/src/M6Web/Component/RedisMock/RedisMockFactory.php:237

Why the signature of method set diffs from predis

Result in mock fail.
The ex and nx parameters diff.
https://github.com/M6Web/RedisMock/blob/7438b0d8f94662d47dbd8931f5944d36f3a93e8e/src/M6Web/Component/RedisMock/RedisMock.php#L80

    public function set($key, $value, $seconds = null)
    {
        if (\is_array($seconds)) {
...

https://github.com/nrk/predis/blob/111d100ee389d624036b46b35ed0c9ac59c71313/src/ClientInterface.php#L63

@method mixed  set($key, $value, $expireResolution = null, $expireTTL = null, $flag = null)

Error starting Vagrant on Mac OS 10.11.5

~/work/RedisMock (getset)> cp Vagrantfile.dist Vagrantfile
~/work/RedisMock (getset)> vagrant up
Bringing machine 'default' up with 'virtualbox' provider...
==> default: Box 'xotelia' could not be found. Attempting to find and install...
    default: Box Provider: virtualbox
    default: Box Version: >= 0
==> default: Box file was not detected as metadata. Adding it directly...
==> default: Adding box 'xotelia' (v0) for provider: virtualbox
    default: Downloading: https://www.dropbox.com/s/mp2h1c8rlbom2sr/xotelia.box?dl=1
The box failed to unpackage properly. Please verify that the box
file you're trying to add is not corrupted and try again. The
output from attempting to unpackage (if any):

bsdtar: Error opening archive: Unrecognized archive format
~/work/RedisMock (getset)> vagrant --version
Vagrant 1.8.1

Project is dead and you should not use it anyway

Sure at the time of writing there was an update back in May 2023 but here is the thing, this project is trying to mimic a Redis server in PHP. No small feat.

We just came out of 2 days of searching why our unit tests would randomly fail. We noticed that "waiting enough" was a "solution" only to realize that the SET method doesn't properly support the EX argument - more specifically Predis allows for $predis->set('my key', 'my value', 'EX', 1000); when this lib would only support $predisMock->set('my key', 'my value', ['EX' => 1000]); - effectively applying a none-determined TTL thus explaining the side-effect.

We then began to fork the project in order to contribute with a fix. We tried to follow the README saying we should use vagrant for running the unit tests - aka https://github.com/BedrockStreaming/RedisMock#tests - so we could TDD ourselves here, only to realize that the referenced image doesn't exist anymore and obviously vagrant is a dead technology now, so...

And it is at that moment we asked ourselves, why? Why would we try to fix this specific behavior when there are probably plenty of others waiting for us to uncover. And most importantly, what do people do in our situation, when they need to "mock Redis"?

Well,

  1. people simply mock the Redis client straight up, mocking the ->set() function, etc.
  2. when you cannot "simply mock Redis" because your unit test is more "functional" than "unit", then you should use... Redis itself 🤷

It's easy and fast to bootstrap a clean Redis instance, especially with docker,

  redis:
    container_name: redis
    restart: unless-stopped
    image: redis:latest
    command: redis-server --loglevel notice

And then with Gitlab CI/CD it is even more easier, https://docs.gitlab.com/ee/ci/services/redis.html

services:
  - redis:latest

I suppose it is also something easy to achieve on Github - https://docs.github.com/en/actions/using-containerized-services/creating-redis-service-containers

Anyway, the point of this is issue is to warn people looking to use redis-mock. That would be nice to have a disclaimer in the README file as well.

I would like to take a moment of gratitude to the dev(s) and maintainer of the project 🙏 even though I think it's a bad idea to go down that route I respect the effort and dedication.

Not working with the Redis extension

Maybe I'm doing it wrong but I cannot get this to work with the Redis extension. I'm running PHP 7, here is the list of warnings I'm getting:

Declaration of M6Web\Component\RedisMock\RedisMock_Redis_Adapter::scan(&$i_iterator, $str_pattern, $i_count) should be compatible with Redis::scan(&$i_iterator, $str_pattern = NULL, $i_count = NULL)
Declaration of M6Web\Component\RedisMock\RedisMock_Redis_Adapter::hscan($str_key, &$i_iterator, $str_pattern, $i_count) should be compatible with Redis::hscan($str_key, &$i_iterator, $str_pattern = NULL, $i_count = NULL)
Declaration of M6Web\Component\RedisMock\RedisMock_Redis_Adapter::zscan($str_key, &$i_iterator, $str_pattern, $i_count) should be compatible with Redis::zscan($str_key, &$i_iterator, $str_pattern = NULL, $i_count = NULL)
Declaration of M6Web\Component\RedisMock\RedisMock_Redis_Adapter::sscan($str_key, &$i_iterator, $str_pattern, $i_count) should be compatible with Redis::sscan($str_key, &$i_iterator, $str_pattern = NULL, $i_count = NULL)

I'm suspecting this is because the default value of internal/extension classes cannot be read (see Ocramius/ProxyManager#162 for example or also ParameterResolver.php#L97).

I'm wondering if it's sane to set a default value of null if we know there is a default value ($parameter->isOptional()) but we can't read it ($parameter->isDefaultValueAvailable())…

Has anyone managed to make this work with the Redis class of the Redis extension? (if so, which PHP version?)

Redis command `eval` is not supported by RedisMock.

Hello!

I work with the Laravel framework and start switching to the latest version (5.8)
I use the josiasmontag / laravel-redis-mock library (which uses your library) to test Laravel queues
Starting with version 5.8, Laravel Redis starts using the function eval, and I get this error

Is it possible to add the eval function to the RedisMock class?

I will be very grateful for this!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.