From 0e7f55dd512be13c5e202628cd663eae607c0a98 Mon Sep 17 00:00:00 2001 From: Florent DUBOST Date: Wed, 18 Dec 2013 11:28:38 +0100 Subject: [PATCH 1/6] Adding possibility to instantiate itself the mock --- README.md | 12 ++- .../Component/RedisMock/RedisMockFactory.php | 73 ++++++++++++++----- tests/units/RedisMockFactory.php | 57 +++++++++++++-- 3 files changed, 113 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index d3aff47..85f5471 100644 --- a/README.md +++ b/README.md @@ -56,8 +56,16 @@ It also mocks **PIPELINE** and **EXECUTE** functions but without any transaction RedisMock library provides a factory able to build a mocked class of your Redis library that can be directly injected in your application : ```php -$factory = new \M6Web\Component\RedisMockFactory(); -$myRedisMock = $factory->getAdapter('My\Redis\Library', new \M6Web\Component\RedisMock\RedisMock()); +$factory = new \M6Web\Component\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 : + +```php +$factory = new \M6Web\Component\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.* diff --git a/src/M6Web/Component/RedisMock/RedisMockFactory.php b/src/M6Web/Component/RedisMock/RedisMockFactory.php index 8ec2afd..c5cb392 100644 --- a/src/M6Web/Component/RedisMock/RedisMockFactory.php +++ b/src/M6Web/Component/RedisMock/RedisMockFactory.php @@ -9,6 +9,7 @@ * The mock can have undesired behavior if your parent class uses unsupported features. * * @author Adrien Samson + * @author Florent Dubost */ class RedisMockFactory { @@ -158,13 +159,24 @@ class RedisMockFactory ); protected $classTemplate = <<<'CLASS' + namespace {{namespace}}; + class {{class}} extends \{{baseClass}} { - protected $mock; - public function __construct($mock) + protected $clientMock; + public function setClientMock($clientMock) + { + $this->clientMock = $clientMock; + } + + public function getClientMock() { - $this->mock = $mock; + if (!isset($this->clientMock)) { + $this->clientMock = new RedisMock(); + } + + return $this->clientMock; } public function __call($method, $args) @@ -172,10 +184,10 @@ public function __call($method, $args) $methodName = strtolower($method); if (!method_exists('M6Web\Component\RedisMock\RedisMock', $methodName)) { - throw new \M6Web\Component\RedisMock\UnsupportedException(sprintf('Redis command `%s` is not supported by RedisMock.', $methodName)); + throw new UnsupportedException(sprintf('Redis command `%s` is not supported by RedisMock.', $methodName)); } - return call_user_func_array(array($this->mock, $methodName), $args); + return call_user_func_array(array($this->getClientMock(), $methodName), $args); } {{methods}} } @@ -185,38 +197,61 @@ public function __call($method, $args) public function {{method}}({{signature}}) { - return $this->mock->{{method}}({{args}}); + return $this->getClientMock()->{{method}}({{args}}); } - METHOD; +protected $constructorTemplate = <<<'CONSTRUCTOR' - public function getAdapter($classToExtend, $redisMock) + public function __construct() { - $newClassName = sprintf('RedisMock_%s_Adapter', str_replace('\\', '_', $classToExtend)); - $namespace = __NAMESPACE__; - $class = $namespace . '\\'. $newClassName; + + } +CONSTRUCTOR; + - if (class_exists($class)) { - return new $class($redisMock); + public function getAdapter($classToExtend) + { + list($namespace, $newClassName, $class) = $this->getAdapterClassName($classToExtend); + + if (!class_exists($class)) { + $classCode = $this->getClassCode($namespace, $newClassName, new \ReflectionClass($classToExtend), true); + eval($classCode); + } + + return new $class(); + } + + public function getAdapterClass($classToExtend) + { + list($namespace, $newClassName, $class) = $this->getAdapterClassName($classToExtend, '_NativeConstructor'); + + if (!class_exists($class)) { + $classCode = $this->getClassCode($namespace, $newClassName, new \ReflectionClass($classToExtend)); + eval($classCode); } - $classCode = $this->getClassCode($namespace, $newClassName, new \ReflectionClass($classToExtend)); + return $class; + } - eval($classCode); + protected function getAdapterClassName($classToExtend, $suffix = '') + { + $newClassName = sprintf('RedisMock_%s_Adapter%s', str_replace('\\', '_', $classToExtend), $suffix); + $namespace = __NAMESPACE__; + $class = $namespace . '\\'. $newClassName; - return new $class($redisMock); + return [$namespace, $newClassName, $class]; } - protected function getClassCode($namespace, $newClassName, \ReflectionClass $class) + protected function getClassCode($namespace, $newClassName, \ReflectionClass $class, $orphanizeConstructor = false) { - $methodsCode = ''; + $methodsCode = $orphanizeConstructor ? $this->constructorTemplate : ''; foreach ($class->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) { $methodName = strtolower($method->getName()); if (!method_exists('M6Web\Component\RedisMock\RedisMock', $methodName) && in_array($methodName, $this->redisCommands)) { - throw new \M6Web\Component\RedisMock\UnsupportedException(sprintf('Redis command `%s` is not supported by RedisMock.', $methodName)); + throw new UnsupportedException(sprintf('Redis command `%s` is not supported by RedisMock.', $methodName)); } elseif (method_exists('M6Web\Component\RedisMock\RedisMock', $methodName)) { $methodsCode .= strtr($this->methodTemplate, array( '{{method}}' => $methodName, diff --git a/tests/units/RedisMockFactory.php b/tests/units/RedisMockFactory.php index c1a38ce..dc4927d 100644 --- a/tests/units/RedisMockFactory.php +++ b/tests/units/RedisMockFactory.php @@ -7,18 +7,19 @@ use mageekguy\atoum\test; /** - * test class for RedisMockFactory + * Test class for RedisMockFactory */ class RedisMockFactory extends test { /** - * test the mock + * Test the mock + * * @return void */ public function testMock() { $factory = new Factory(); - $mock = $factory->getAdapter('StdClass', new Mock()); + $mock = $factory->getAdapter('StdClass'); $this->assert ->object($mock) @@ -48,7 +49,7 @@ public function testMock() }) ->isInstanceOf('\M6Web\Component\RedisMock\UnsupportedException'); - $mock2 = $factory->getAdapter('StdClass', new Mock()); + $mock2 = $factory->getAdapter('StdClass'); $this->assert ->object($mock2) @@ -58,13 +59,14 @@ public function testMock() } /** - * test the mock with a complex base class + * Test the mock with a complex base class + * * @return void */ public function testMockComplex() { $factory = new Factory(); - $mock = $factory->getAdapter('M6Web\Component\RedisMock\tests\units\RedisWithMethods', new Mock()); + $mock = $factory->getAdapter('M6Web\Component\RedisMock\tests\units\RedisWithMethods'); $this->assert ->object($mock) @@ -101,13 +103,44 @@ public function testMockComplex() )); } + /** + * Test the mock with a base class that implement unsupported Redis commands + * + * @return void + */ public function testUnsupportedMock() { $factory = new Factory(); $this->assert ->exception(function() use ($factory) { - $factory->getAdapter('M6Web\Component\RedisMock\Adapter\tests\units\RedisWithUnsupportedMethods', new Mock()); - }); + $factory->getAdapter('M6Web\Component\RedisMock\tests\units\RedisWithUnsupportedMethods'); + }) + ->isInstanceOf('\M6Web\Component\RedisMock\UnsupportedException'); + } + + /** + * Test method getAdpaterClass + * + * @return void + */ + public function testGetAdapterClass() + { + $factory = new Factory(); + $this->assert + ->string($class = $factory->getAdapterClass('M6Web\Component\RedisMock\tests\units\RedisWithNativeConstructor')) + ->isEqualTo('M6Web\Component\RedisMock\RedisMock_M6Web_Component_RedisMock_tests_units_RedisWithNativeConstructor_Adapter_NativeConstructor') + ->class($class) + ->extends('M6Web\Component\RedisMock\tests\units\RedisWithNativeConstructor') + ->when(function() use ($class) { + $mock = new $class(); + }) + ->error() + ->exists() + ->when(function() use ($class) { + $mock = new $class(null); + }) + ->error() + ->notExists(); } } @@ -145,4 +178,12 @@ public function punsubscribe($pattern = null) { throw new \Exception('Not mocked'); } +} + +class RedisWithNativeConstructor +{ + public function __construct($param) + { + + } } \ No newline at end of file From ad54999b8eb28fb98bf570ada7c9695e4bfdde38 Mon Sep 17 00:00:00 2001 From: Florent Dubost Date: Wed, 18 Dec 2013 11:31:31 +0100 Subject: [PATCH 2/6] Too much spaces --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 85f5471..ea62817 100644 --- a/README.md +++ b/README.md @@ -64,8 +64,8 @@ $myRedisMock = new $myRedisMockClass($myParameters); In a simpler way, if you don't need to instanciate the mocked class with custom parameters : ```php -$factory = new \M6Web\Component\RedisMockFactory(); -$myRedisMock = $factory->getAdapter('My\Redis\Library'); +$factory = new \M6Web\Component\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.* From 6a750ac24721c1c728246074b1ef99405abd7807 Mon Sep 17 00:00:00 2001 From: Florent Dubost Date: Wed, 18 Dec 2013 11:36:18 +0100 Subject: [PATCH 3/6] Namespace correction --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ea62817..053ff22 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ It also mocks **PIPELINE** and **EXECUTE** functions but without any transaction RedisMock library provides a factory able to build a mocked class of your Redis library that can be directly injected in your application : ```php -$factory = new \M6Web\Component\RedisMockFactory(); +$factory = new \M6Web\Component\RedisMock\RedisMockFactory(); $myRedisMockClass = $factory->getAdapterClass('My\Redis\Library'); $myRedisMock = new $myRedisMockClass($myParameters); ``` @@ -64,7 +64,7 @@ $myRedisMock = new $myRedisMockClass($myParameters); In a simpler way, if you don't need to instanciate the mocked class with custom parameters : ```php -$factory = new \M6Web\Component\RedisMockFactory(); +$factory = new \M6Web\Component\RedisMock\RedisMockFactory(); $myRedisMock = $factory->getAdapter('My\Redis\Library'); ``` From 59e43b0c60464a09c711efacc7a20c466866e044 Mon Sep 17 00:00:00 2001 From: Florent Dubost Date: Wed, 18 Dec 2013 11:55:45 +0100 Subject: [PATCH 4/6] Update README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 053ff22..354459a 100644 --- a/README.md +++ b/README.md @@ -61,13 +61,15 @@ $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 : +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) : ```php $factory = new \M6Web\Component\RedisMock\RedisMockFactory(); $myRedisMock = $factory->getAdapter('My\Redis\Library'); ``` +For instance, it is easier to inject the mock using Symfony config file. + **WARNING !** *RedisMock doesn't implement all Redis features and commands. The mock can have undesired behavior if your parent class uses unsupported features.* ## Tests From 3fa4fd30fae4f673d1046c7315b367b675a1bf09 Mon Sep 17 00:00:00 2001 From: Florent Dubost Date: Wed, 18 Dec 2013 11:56:39 +0100 Subject: [PATCH 5/6] Update README.md --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 354459a..207f263 100644 --- a/README.md +++ b/README.md @@ -68,8 +68,6 @@ $factory = new \M6Web\Component\RedisMock\RedisMockFactory(); $myRedisMock = $factory->getAdapter('My\Redis\Library'); ``` -For instance, it is easier to inject the mock using Symfony config file. - **WARNING !** *RedisMock doesn't implement all Redis features and commands. The mock can have undesired behavior if your parent class uses unsupported features.* ## Tests From 9adcff045af760891f91e490c839e805d5236498 Mon Sep 17 00:00:00 2001 From: Florent Dubost Date: Wed, 18 Dec 2013 14:18:21 +0100 Subject: [PATCH 6/6] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 207f263..71091de 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ $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) : +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 : ```php $factory = new \M6Web\Component\RedisMock\RedisMockFactory();