From 17eb8b672c62cd55693dc5a792b7965fd54d5755 Mon Sep 17 00:00:00 2001 From: Reece May Date: Sat, 4 May 2019 19:19:51 +0200 Subject: [PATCH] Feature/readable output (#2) * feat: add a readable output to the calls done * feat: mock recalls vars set and also prints them * refac: edits * fix: changed the regex and allowd ->all() = __args * feat: add tests for the mockery of mocks * feat: add ci/codecov * foobar * fix: ci and coverage reports * feat: badges :) * feat: improve tests * feat: dump the contents of the value set * fix: tests to account the colection wrapping * fix: too much arrayness * fix: grammar and readme --- .gitignore | 1 + .travis.yml | 32 ++++++ README.md | 15 ++- _docs/README.md | 22 ++++ composer.json | 10 ++ phpunit.xml | 32 ++++++ src/Mocked.php | 143 ++++++++++++++++++++---- src/ReflectionMockery.php | 44 ++++++-- src/Utils/VarStore.php | 47 ++++++++ tests/ClassOfTest.php | 34 ++++++ tests/Feature/ReflectionMockeryTest.php | 70 ++++++++++++ tests/TestCase.php | 8 ++ tests/Unit/MockedTest.php | 42 +++++++ 13 files changed, 466 insertions(+), 34 deletions(-) create mode 100644 .travis.yml create mode 100644 _docs/README.md create mode 100644 phpunit.xml create mode 100644 src/Utils/VarStore.php create mode 100644 tests/ClassOfTest.php create mode 100644 tests/Feature/ReflectionMockeryTest.php create mode 100644 tests/TestCase.php create mode 100644 tests/Unit/MockedTest.php diff --git a/.gitignore b/.gitignore index 987e2a2..e96516b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ composer.lock vendor +.phpunit.result.cache diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..c7e83f1 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,32 @@ +dist: xenial +language: php + +env: + global: + - SETUP=stable + +matrix: + fast_finish: true + include: + # - php: 7.2 + - php: 7.2 + env: SETUP=lowest + # - php: 7.3 + - php: 7.3 + env: SETUP=lowest + +cache: + directories: + - $HOME/.composer/cache + +before_install: + - travis_retry composer self-update + +install: + - if [[ $SETUP = 'stable' ]]; then travis_retry composer update --prefer-dist --no-interaction --prefer-stable --no-suggest; fi + - if [[ $SETUP = 'lowest' ]]; then travis_retry composer update --prefer-dist --no-interaction --prefer-lowest --prefer-stable --no-suggest; fi + +script: vendor/bin/phpunit --coverage-clover=coverage.xml --coverage-text + +after_success: + - bash <(curl -s https://codecov.io/bash) \ No newline at end of file diff --git a/README.md b/README.md index 3595dab..edf4557 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,17 @@ # Reflection Mocker

+ + + + + + Latest Stable Version License Downloads

-This package is initially made to fi an issue on the MailEclipse package, but improvements are welcome. +This package is initially made to for an issue on the MailEclipse package, but improvements are welcome. It currently is probably stupid simple, but deals with the one job of reading a file and mocking it. > Generate a mocked instance of the un-typed params in a __construct() method @@ -53,6 +59,13 @@ $mock = new ReflectionMockery(new \ReflectionClass('\App\User')); * Use call a variable from the class that don't exist */ {{ $mock->get('somethingNotInUser') }} +{{ $mock->somethingNotInUser }} + +// both would return + +"mock->somethingNotInUser" +// if something was set in user +'mock->somethingNotInUser => ["value that set"]' ``` diff --git a/_docs/README.md b/_docs/README.md new file mode 100644 index 0000000..8af76f7 --- /dev/null +++ b/_docs/README.md @@ -0,0 +1,22 @@ +# Example + +```php +name->class = 'Chips'; + +echo $mocked->name->class . PHP_EOL; + +echo $mocked->name->class->data . PHP_EOL; + +die(1); + +``` \ No newline at end of file diff --git a/composer.json b/composer.json index acab947..752e86b 100644 --- a/composer.json +++ b/composer.json @@ -5,6 +5,10 @@ "illuminate/support": "^5.6", "illuminate/filesystem": "^5.6" }, + "require-dev": { + "phpunit/phpunit": "^7.5", + "orchestra/testbench": "^3.5" + }, "keywords": [ "laravel", "mockery", "helper", "testing" ], @@ -18,9 +22,15 @@ ], "homepage": "https://github.com/reecem/mocker", "minimum-stability": "dev", + "prefer-stable": true, "autoload": { "psr-4": { "ReeceM\\Mocker\\": "src/" } + }, + "autoload-dev": { + "psr-4": { + "Tests\\": "tests/" + } } } diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..646fed8 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,32 @@ + + + + + ./tests/Unit + + + + ./tests/Feature + + + + + ./src + + + + + + + + + + diff --git a/src/Mocked.php b/src/Mocked.php index 03823be..5550028 100644 --- a/src/Mocked.php +++ b/src/Mocked.php @@ -3,44 +3,145 @@ namespace ReeceM\Mocker; use Illuminate\Support\Arr; +use ReeceM\Mocker\Utils\VarStore; /** * the default mocked class that has dynamic variable names and setting of them too * this is what makes tha fake objects that result from reading un-typed params */ class Mocked { - private $basename; - private $vars = []; - - public function __construct($_basename) + + /** + * the base array from the new call + */ + private $base; + + /** + * the previous calls to the method + */ + private $previous; + + /** + * vars set + * @var array $vars + */ + private $vars; + + /** + * Singleton storage + * @var \ReeceM\Mocker\Utils\VarStore $store + */ + private $store; + + /** + * the combined class list to dump for to string + * @var array $trace + */ + private $trace = []; + + private static $GET_METHOD = "__get"; + private static $SET_METHOD = "__set"; + + /* + * The mocked constructor + * @param string|array $base the name of the arg/object (buttery biscuit base) + * @param \ReeceM\Mocker\Utils\VarStore $store singleton variable storage + * @param mixed $previous the base of the calling class + */ + public function __construct($base, VarStore $store, $previous = []) { - $this->basename = $_basename; + $this->previous = $previous; + $this->store = $store; + $this->base = $base; + + if(is_string($base)) { + $this->base = [['args' => [$base], 'function' => static::$GET_METHOD]]; + } + + $this->structureMockeryCalls(); } - public function __get($name) + /** + * takes the debug trace and structures the single level call + * @todo ensure to implement a call limit on this... + */ + private function structureMockeryCalls() { - return Arr::get($this->vars, $name, $this); + $toSet = null; + try { + $args = Arr::get($this->base[0], 'args', []); // only one if its a get command + $function = Arr::get($this->base[0], 'function', '__get'); + // $type = Arr::get($this->base[0], 'type', ''); '->' / '::' + if($function == self::$GET_METHOD) + { + // merge the preceding calls with this one + array_push($this->previous, $args[0]); + $this->trace = $this->previous; + } else { + array_push($this->previous, $args[0]); + $this->trace = $this->previous; + $toSet = $args[1]; + } + return $this->setMockeryVariables($args[0], $toSet); - // return __(implode('.', [ - // 'mocked', - // $this->basename, - // $name - // ])); + } catch (\Exception $th) { + throw $th; + } } - public function __set($name, $value) + private function setMockeryVariables($key, $value = null) { - $this->vars[$name] = $value; - // return $name . ' ' . $value; + $memorable = $this->store->memoized; + + $memorable[$key] = $value; + + $this->vars = array_merge($memorable, $this->store->memoized); + + $this->store->memoized = $this->vars; } - public function __toString() + public function __get($name) { - return 'mocked ' . $this->basename; /** - * @todo make this return a translation instance for the basename - * allows user define-able test vars + * @todo maybe return the value of the variable if it has been set and has a value */ - return __('mocked.' . $this->basename . '_to_string'); + return new Mocked(debug_backtrace(false, 1), $this->store, $this->trace); + } + + /** + * Set a method to the calls + */ + public function __call($name, $arguments) + { + // return new Mocked() + } + + /** + * set the value of something inside the class + */ + public function __set($name, $value) + { + return new Mocked(debug_backtrace(false, 1), $this->store, $this->trace); + } + + /** + * @todo implement __callStatic + */ + + /** + * Return a string of the called object + * would be at the end of the whole thing + * @param void + * @return string + */ + public function __toString() + { + $calledValue = $this->store->memoized[array_reverse($this->trace)[0]] ?? null; + + if($calledValue != null) { + return implode("->", $this->trace) . ' => ' . collect($calledValue); + } + + return implode("->", $this->trace); } -} \ No newline at end of file +} diff --git a/src/ReflectionMockery.php b/src/ReflectionMockery.php index 1cca33c..f47589d 100644 --- a/src/ReflectionMockery.php +++ b/src/ReflectionMockery.php @@ -2,6 +2,8 @@ namespace ReeceM\Mocker; +use ReeceM\Mocker\Mocked; +use ReeceM\Mocker\Utils\VarStore; use Illuminate\Filesystem\Filesystem; use Illuminate\Support\Arr; @@ -20,7 +22,7 @@ class ReflectionMockery { /** * all the args for the class */ - public $args; + public $__args; /** * Construct the Reflection Mocker Class @@ -44,36 +46,54 @@ public function __construct($reflect) $this->file = (new Filesystem())->get($reflection->getFileName()); $this->reflection = $reflection; - $this->extractWantedArgs(); + $this->reflectionExtractWantedArgs(); } - public function newClass(string $name = 'Mocked') : Mocked + public function reflectionNewClass(string $name = 'ReflectionMockery') : Mocked { - return new Mocked($name); + return new Mocked($name, VarStore::singleton()); } - public function extractWantedArgs() + public function reflectionExtractWantedArgs() { $params = $this->reflection->getConstructor()->getParameters(); foreach($params as $param) { $matches = []; $argName = $param->name; - $result = $this->newClass($argName); - preg_match_all('/(?\$' . $argName . '->.*[^;\n])/', $this->file, $matches); - + $result = $this->reflectionNewClass($argName); + // preg_match_all('/(?\$' . $argName . '->.*[^;\n])/', $this->file, $matches); + preg_match_all('/(?\$' . $argName . '->.+?\b)/', $this->file, $matches); + foreach ($matches['matched'] as $key) { $name = preg_replace('/(\$' . $argName . '->)/', '', $key); - $result->$name = $this->newClass($argName); + $result->$name = $this->reflectionNewClass($argName); } - Arr::set($this->args, $argName, $result); + Arr::set($this->__args, $argName, $result); + } + } + + public function __get($value) : Mocked + { + if ($value == '__args') { + return $this->__args; } + return Arr::get($this->__args, $value, $this->reflectionNewClass()); } - public function get($arg = null) + public function get($value) : Mocked { - return Arr::get($this->args, $arg, $this->newClass()); + return Arr::get($this->__args, $value, $this->reflectionNewClass()); } + /** + * return an array of all the arguments found + * @param array $exclude + * @return array + */ + public function all($exclude = []) : array + { + return Arr::except($this->__args, $exclude); + } } \ No newline at end of file diff --git a/src/Utils/VarStore.php b/src/Utils/VarStore.php new file mode 100644 index 0000000..209e925 --- /dev/null +++ b/src/Utils/VarStore.php @@ -0,0 +1,47 @@ +$name = $value; + } + + public function __get($name) + { + return $this->$name; + } +} diff --git a/tests/ClassOfTest.php b/tests/ClassOfTest.php new file mode 100644 index 0000000..fe2a31a --- /dev/null +++ b/tests/ClassOfTest.php @@ -0,0 +1,34 @@ +all() + * @param mixed $data + */ + public function __construct(Mocked $user, $data) + { + $data->complex->var->that->is->set->too = "Hello World"; + + $this->user = $user; + $this->aData = $data->firstValue; + $this->bData = $data->complex->var->that->is->set->too; + } + + /** + * Return a string to trigger __toString() + */ + public function __invoke() : string + { + return (string)$this->bData; + } +} \ No newline at end of file diff --git a/tests/Feature/ReflectionMockeryTest.php b/tests/Feature/ReflectionMockeryTest.php new file mode 100644 index 0000000..c928aa0 --- /dev/null +++ b/tests/Feature/ReflectionMockeryTest.php @@ -0,0 +1,70 @@ +newInstanceArgs($mock->all()); + // run the invoke function to use the instantiated data + // the result is a collection instance + $this->assertSame('data->complex->var->that->is->set->too => ["Hello World"]', $instance()); + } + /** + * A basic test example. + * @test + * @return void + */ + public function reflection_accepts_string_test() + { + $mock = new ReflectionMockery('Tests\ClassOfTest'); + + $class = new \ReflectionClass('Tests\ClassOfTest'); + + $instance = $class->newInstanceArgs($mock->all()); + // run the invoke function to use the instantiated data + $this->assertSame('data->complex->var->that->is->set->too => ["Hello World"]', $instance()); + } + /** + * A basic test example. + * @test + * @return void + */ + public function reflection_gives_magic_get_test() + { + $mock = new ReflectionMockery('Tests\ClassOfTest'); + + $this->assertInstanceOf(\ReeceM\Mocker\Mocked::class, $mock->data); + $this->assertInstanceOf(\ReeceM\Mocker\Mocked::class, $mock->get('data')); + $this->assertInstanceOf(\ReeceM\Mocker\Mocked::class, $mock->user); + $this->assertInstanceOf(\ReeceM\Mocker\Mocked::class, $mock->get('user')); + } + + /** + * A basic test example. + * @test + * @return void + */ + public function reflection_fails_on_wrong_data_test() + { + try { + $mocked = new ReflectionMockery(new \Exception('Nope')); + } catch (\Exception $e) { + $this->assertTrue(true); + return; + } + } +} diff --git a/tests/TestCase.php b/tests/TestCase.php new file mode 100644 index 0000000..ea8ee0d --- /dev/null +++ b/tests/TestCase.php @@ -0,0 +1,8 @@ +name->class = 'test'; + + $this->assertContains('=> ["test"]', (string)$mocked->name->class); + $this->assertSame('user->name->class => ["test"]', (string)$mocked->name->class); + $this->assertNotSame('user->name->class => ["test"]', (string)$mocked->name->class->data); + } + /** + * Test if the un-set mocked returns the calls chained + * @test + * @return void + */ + public function mocked_returns_chained_only_test() + { + $mocked = new Mocked('user', VarStore::singleton()); + + $this->assertSame('user', (string)$mocked); + $this->assertSame('user->name', (string)$mocked->name); + // singleton persists + $this->assertNotSame('user->name->class', (string)$mocked->name->class); + $this->assertSame('user->name->class->data', (string)$mocked->name->class->data); + } + +} \ No newline at end of file