From 8099d593789b13afdaa1282912aa126f7d779381 Mon Sep 17 00:00:00 2001 From: "Jonathan H. Wage" Date: Thu, 13 Dec 2018 04:07:14 +0000 Subject: [PATCH] Introduce the use of nikic/php-parser to generate the phpunit test class. --- README.md | 6 +- composer.json | 5 +- composer.lock | 385 ++++++++++-------- .../ComposerConfigurationReader.php | 5 +- lib/Templates/test-class.html.twig | 69 ---- lib/TestClassGenerator.php | 308 ++++++++++++-- lib/TestClassMetadataParser.php | 200 ++++----- lib/Writer/Psr4TestClassWriter.php | 19 +- .../ComposerConfigurationReaderTest.php | 37 ++ .../ConfigurationBuilderTest.php | 75 ++++ tests/GeneratedTestClassTest.php | 51 +++ tests/InflectorFactoryTest.php | 18 + tests/{TestClass.php => TestClass1.php} | 40 +- tests/TestClass2.php | 12 + tests/TestClassGeneratorTest.php | 109 +++-- tests/TestClassMetadataTest.php | 61 +++ tests/Writer/Psr4TestClassWriterTest.php | 108 +++++ 17 files changed, 1103 insertions(+), 405 deletions(-) delete mode 100644 lib/Templates/test-class.html.twig create mode 100644 tests/Configuration/ComposerConfigurationReaderTest.php create mode 100644 tests/Configuration/ConfigurationBuilderTest.php create mode 100644 tests/GeneratedTestClassTest.php create mode 100644 tests/InflectorFactoryTest.php rename tests/{TestClass.php => TestClass1.php} (61%) create mode 100644 tests/TestClass2.php create mode 100644 tests/TestClassMetadataTest.php create mode 100644 tests/Writer/Psr4TestClassWriterTest.php diff --git a/README.md b/README.md index 054eb9b..4ad10fa 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ namespace App\Services; class Dependency { - public function getSomething() + public function getSomething() : null { return null; } @@ -99,12 +99,12 @@ class MyServiceTest extends TestCase public function testGetDependency() : void { - $this->myService->getDependency(); + self::assertInstanceOf(Dependency::class, $this->myService->getDependency()); } public function testGetValue() : void { - $this->myService->getValue(); + self::assertSame(1, $this->myService->getValue()); } protected function setUp() : void diff --git a/composer.json b/composer.json index a29ea20..faf1106 100644 --- a/composer.json +++ b/composer.json @@ -12,8 +12,9 @@ "require": { "php": "^7.1", "doctrine/inflector": "2.0.x-dev", - "twig/twig": "^2.5", - "symfony/console": "^4.1" + "nikic/php-parser": "^4.1", + "symfony/console": "^4.1", + "symfony/filesystem": "^4.2" }, "require-dev": { "doctrine/coding-standard": "^5.0", diff --git a/composer.lock b/composer.lock index a32c2e2..6ac9b7d 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "d149a8e30e294b901989cede7cebf0d1", + "content-hash": "9da2fd1b6db1d69d9d05078e5b39ed47", "packages": [ { "name": "doctrine/inflector", @@ -83,22 +83,74 @@ ], "time": "2018-10-29T15:15:33+00:00" }, + { + "name": "nikic/php-parser", + "version": "v4.1.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "d0230c5c77a7e3cfa69446febf340978540958c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/d0230c5c77a7e3cfa69446febf340978540958c0", + "reference": "d0230c5c77a7e3cfa69446febf340978540958c0", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.5 || ^7.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.1-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "time": "2018-10-10T09:24:14+00:00" + }, { "name": "symfony/console", - "version": "v4.1.7", + "version": "v4.2.1", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "432122af37d8cd52fba1b294b11976e0d20df595" + "reference": "4dff24e5d01e713818805c1862d2e3f901ee7dd0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/432122af37d8cd52fba1b294b11976e0d20df595", - "reference": "432122af37d8cd52fba1b294b11976e0d20df595", + "url": "https://api.github.com/repos/symfony/console/zipball/4dff24e5d01e713818805c1862d2e3f901ee7dd0", + "reference": "4dff24e5d01e713818805c1862d2e3f901ee7dd0", "shasum": "" }, "require": { "php": "^7.1.3", + "symfony/contracts": "^1.0", "symfony/polyfill-mbstring": "~1.0" }, "conflict": { @@ -122,7 +174,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } }, "autoload": { @@ -149,7 +201,125 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2018-10-31T09:30:44+00:00" + "time": "2018-11-27T07:40:44+00:00" + }, + { + "name": "symfony/contracts", + "version": "v1.0.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/contracts.git", + "reference": "1aa7ab2429c3d594dd70689604b5cf7421254cdf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/contracts/zipball/1aa7ab2429c3d594dd70689604b5cf7421254cdf", + "reference": "1aa7ab2429c3d594dd70689604b5cf7421254cdf", + "shasum": "" + }, + "require": { + "php": "^7.1.3" + }, + "require-dev": { + "psr/cache": "^1.0", + "psr/container": "^1.0" + }, + "suggest": { + "psr/cache": "When using the Cache contracts", + "psr/container": "When using the Service contracts", + "symfony/cache-contracts-implementation": "", + "symfony/service-contracts-implementation": "", + "symfony/translation-contracts-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\": "" + }, + "exclude-from-classmap": [ + "**/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A set of abstractions extracted out of the Symfony components", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "time": "2018-12-05T08:06:11+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v4.2.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "2f4c8b999b3b7cadb2a69390b01af70886753710" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/2f4c8b999b3b7cadb2a69390b01af70886753710", + "reference": "2f4c8b999b3b7cadb2a69390b01af70886753710", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "symfony/polyfill-ctype": "~1.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.2-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Filesystem Component", + "homepage": "https://symfony.com", + "time": "2018-11-11T19:52:12+00:00" }, { "name": "symfony/polyfill-ctype", @@ -267,88 +437,21 @@ "shim" ], "time": "2018-09-21T13:07:52+00:00" - }, - { - "name": "twig/twig", - "version": "v2.5.0", - "source": { - "type": "git", - "url": "https://github.com/twigphp/Twig.git", - "reference": "6a5f676b77a90823c2d4eaf76137b771adf31323" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/6a5f676b77a90823c2d4eaf76137b771adf31323", - "reference": "6a5f676b77a90823c2d4eaf76137b771adf31323", - "shasum": "" - }, - "require": { - "php": "^7.0", - "symfony/polyfill-ctype": "^1.8", - "symfony/polyfill-mbstring": "~1.0" - }, - "require-dev": { - "psr/container": "^1.0", - "symfony/debug": "^2.7", - "symfony/phpunit-bridge": "^3.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.5-dev" - } - }, - "autoload": { - "psr-0": { - "Twig_": "lib/" - }, - "psr-4": { - "Twig\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com", - "homepage": "http://fabien.potencier.org", - "role": "Lead Developer" - }, - { - "name": "Armin Ronacher", - "email": "armin.ronacher@active-4.com", - "role": "Project Founder" - }, - { - "name": "Twig Team", - "homepage": "https://twig.symfony.com/contributors", - "role": "Contributors" - } - ], - "description": "Twig, the flexible, fast, and secure template language for PHP", - "homepage": "https://twig.symfony.com", - "keywords": [ - "templating" - ], - "time": "2018-07-13T07:18:09+00:00" } ], "packages-dev": [ { "name": "composer/xdebug-handler", - "version": "1.3.0", + "version": "1.3.1", "source": { "type": "git", "url": "https://github.com/composer/xdebug-handler.git", - "reference": "b8e9745fb9b06ea6664d8872c4505fb16df4611c" + "reference": "dc523135366eb68f22268d069ea7749486458562" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/b8e9745fb9b06ea6664d8872c4505fb16df4611c", - "reference": "b8e9745fb9b06ea6664d8872c4505fb16df4611c", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/dc523135366eb68f22268d069ea7749486458562", + "reference": "dc523135366eb68f22268d069ea7749486458562", "shasum": "" }, "require": { @@ -379,7 +482,7 @@ "Xdebug", "performance" ], - "time": "2018-08-31T19:07:57+00:00" + "time": "2018-11-29T10:59:02+00:00" }, { "name": "dealerdirect/phpcodesniffer-composer-installer", @@ -1140,57 +1243,6 @@ ], "time": "2018-09-18T10:22:16+00:00" }, - { - "name": "nikic/php-parser", - "version": "v4.1.0", - "source": { - "type": "git", - "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "d0230c5c77a7e3cfa69446febf340978540958c0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/d0230c5c77a7e3cfa69446febf340978540958c0", - "reference": "d0230c5c77a7e3cfa69446febf340978540958c0", - "shasum": "" - }, - "require": { - "ext-tokenizer": "*", - "php": ">=7.0" - }, - "require-dev": { - "phpunit/phpunit": "^6.5 || ^7.0" - }, - "bin": [ - "bin/php-parse" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.1-dev" - } - }, - "autoload": { - "psr-4": { - "PhpParser\\": "lib/PhpParser" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Nikita Popov" - } - ], - "description": "A PHP parser written in PHP", - "keywords": [ - "parser", - "php" - ], - "time": "2018-10-10T09:24:14+00:00" - }, { "name": "ocramius/package-versions", "version": "1.3.0", @@ -1605,16 +1657,16 @@ }, { "name": "phpstan/phpstan", - "version": "0.10.5", + "version": "0.10.6", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "c6a8cd1fe08a23b9d101a55ffa9ff6b91d71ef5d" + "reference": "f0252a5ab6b4a293fb25f218d9c64386f272280f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/c6a8cd1fe08a23b9d101a55ffa9ff6b91d71ef5d", - "reference": "c6a8cd1fe08a23b9d101a55ffa9ff6b91d71ef5d", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/f0252a5ab6b4a293fb25f218d9c64386f272280f", + "reference": "f0252a5ab6b4a293fb25f218d9c64386f272280f", "shasum": "" }, "require": { @@ -1630,6 +1682,9 @@ "symfony/console": "~3.2 || ~4.0", "symfony/finder": "~3.2 || ~4.0" }, + "conflict": { + "symfony/console": "3.4.16 || 4.1.5" + }, "require-dev": { "brianium/paratest": "^2.0", "consistence/coding-standard": "^3.5", @@ -1646,7 +1701,8 @@ "phpstan/phpstan-phpunit": "^0.10", "phpstan/phpstan-strict-rules": "^0.10", "phpunit/phpunit": "^7.0", - "slevomat/coding-standard": "^4.7.2" + "slevomat/coding-standard": "^4.7.2", + "squizlabs/php_codesniffer": "^3.3.2" }, "bin": [ "bin/phpstan" @@ -1670,7 +1726,7 @@ "MIT" ], "description": "PHPStan - PHP Static Analysis Tool", - "time": "2018-10-20T17:24:55+00:00" + "time": "2018-12-04T07:28:04+00:00" }, { "name": "phpstan/phpstan-deprecation-rules", @@ -2068,16 +2124,16 @@ }, { "name": "phpunit/phpunit", - "version": "7.4.4", + "version": "7.5.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "b1be2c8530c4c29c3519a052c9fb6cee55053bbd" + "reference": "c23d78776ad415d5506e0679723cb461d71f488f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/b1be2c8530c4c29c3519a052c9fb6cee55053bbd", - "reference": "b1be2c8530c4c29c3519a052c9fb6cee55053bbd", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c23d78776ad415d5506e0679723cb461d71f488f", + "reference": "c23d78776ad415d5506e0679723cb461d71f488f", "shasum": "" }, "require": { @@ -2098,7 +2154,7 @@ "phpunit/php-timer": "^2.0", "sebastian/comparator": "^3.0", "sebastian/diff": "^3.0", - "sebastian/environment": "^3.1 || ^4.0", + "sebastian/environment": "^4.0", "sebastian/exporter": "^3.1", "sebastian/global-state": "^2.0", "sebastian/object-enumerator": "^3.0.3", @@ -2122,7 +2178,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "7.4-dev" + "dev-master": "7.5-dev" } }, "autoload": { @@ -2148,20 +2204,20 @@ "testing", "xunit" ], - "time": "2018-11-14T16:52:02+00:00" + "time": "2018-12-12T07:20:32+00:00" }, { "name": "psr/log", - "version": "1.0.2", + "version": "1.1.0", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d" + "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", - "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", + "url": "https://api.github.com/repos/php-fig/log/zipball/6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd", + "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd", "shasum": "" }, "require": { @@ -2195,7 +2251,7 @@ "psr", "psr-3" ], - "time": "2016-10-10T12:19:37+00:00" + "time": "2018-11-20T15:27:04+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", @@ -2364,28 +2420,28 @@ }, { "name": "sebastian/environment", - "version": "3.1.0", + "version": "4.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5" + "reference": "febd209a219cea7b56ad799b30ebbea34b71eb8f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/cd0871b3975fb7fc44d11314fd1ee20925fce4f5", - "reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/febd209a219cea7b56ad799b30ebbea34b71eb8f", + "reference": "febd209a219cea7b56ad799b30ebbea34b71eb8f", "shasum": "" }, "require": { - "php": "^7.0" + "php": "^7.1" }, "require-dev": { - "phpunit/phpunit": "^6.1" + "phpunit/phpunit": "^7.4" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.1.x-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -2410,7 +2466,7 @@ "environment", "hhvm" ], - "time": "2017-07-01T08:51:00+00:00" + "time": "2018-11-25T09:31:21+00:00" }, { "name": "sebastian/exporter", @@ -2852,16 +2908,16 @@ }, { "name": "symfony/finder", - "version": "v4.1.7", + "version": "v4.2.1", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "1f17195b44543017a9c9b2d437c670627e96ad06" + "reference": "e53d477d7b5c4982d0e1bfd2298dbee63d01441d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/1f17195b44543017a9c9b2d437c670627e96ad06", - "reference": "1f17195b44543017a9c9b2d437c670627e96ad06", + "url": "https://api.github.com/repos/symfony/finder/zipball/e53d477d7b5c4982d0e1bfd2298dbee63d01441d", + "reference": "e53d477d7b5c4982d0e1bfd2298dbee63d01441d", "shasum": "" }, "require": { @@ -2870,7 +2926,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } }, "autoload": { @@ -2897,7 +2953,7 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "time": "2018-10-03T08:47:56+00:00" + "time": "2018-11-11T19:52:12+00:00" }, { "name": "theseer/tokenizer", @@ -3000,8 +3056,5 @@ "platform": { "php": "^7.1" }, - "platform-dev": [], - "platform-overrides": { - "php": "7.1.3" - } + "platform-dev": [] } diff --git a/lib/Configuration/ComposerConfigurationReader.php b/lib/Configuration/ComposerConfigurationReader.php index 73cdf6e..7bf880a 100644 --- a/lib/Configuration/ComposerConfigurationReader.php +++ b/lib/Configuration/ComposerConfigurationReader.php @@ -17,9 +17,10 @@ class ComposerConfigurationReader { - public function createConfiguration() : Configuration + public function createConfiguration(?string $path = null) : Configuration { - $composerJsonPath = getcwd() . '/composer.json'; + $path = $path ?? getcwd(); + $composerJsonPath = $path . '/composer.json'; if (! file_exists($composerJsonPath)) { throw new RuntimeException( diff --git a/lib/Templates/test-class.html.twig b/lib/Templates/test-class.html.twig deleted file mode 100644 index 85df74a..0000000 --- a/lib/Templates/test-class.html.twig +++ /dev/null @@ -1,69 +0,0 @@ -createMock({{ body.parameterType }}::class); -{% endfor %} -{% for body in testMethod.body if body.type == 'normal' %} - ${{ body.parameterName }} = ''; -{% endfor %} -{% for body in testMethod.body if body.type == 'sut' %} -{% if body.parameters|length == 0 %} - $this->{{ body.parameterName }}->{{ body.methodName }}();{% else %} - $this->{{ body.parameterName }}->{{ body.methodName }}( -{% for parameter in body.parameters %} - ${{ parameter }}{% if not loop.last %},{% endif %} - -{% endfor %} - ); -{% endif %} -{% endfor %} - - } - -{% endfor %} - protected function setUp() : void - { -{% for setUpDependency in testClassMetadata.setUpDependencies if setUpDependency.type == 'dependency' %} - $this->{{ setUpDependency.propertyName }} = $this->createMock({{ setUpDependency.propertyType }}::class); -{% endfor %} -{% for setUpDependency in testClassMetadata.setUpDependencies if setUpDependency.type == 'normal' %} - $this->{{ setUpDependency.propertyName }} = {{ setUpDependency.propertyValue }}; -{% endfor %} - -{% for setUpDependency in testClassMetadata.setUpDependencies if setUpDependency.type == 'sut' %} -{% if setUpDependency.parameters|length > 0 %} - $this->{{ setUpDependency.propertyName }} = new {{ setUpDependency.propertyType }}( -{% for parameter in setUpDependency.parameters %} - $this->{{ parameter }}{% if not loop.last %},{% endif %} - -{% endfor %} - ); -{% else %} - $this->{{ setUpDependency.propertyName }} = new {{ setUpDependency.propertyType }}(); -{% endif %} -{% endfor %} - } -} diff --git a/lib/TestClassGenerator.php b/lib/TestClassGenerator.php index 600c51f..884acb2 100644 --- a/lib/TestClassGenerator.php +++ b/lib/TestClassGenerator.php @@ -6,9 +6,16 @@ use Doctrine\Inflector\Inflector; use JWage\PHPUnitTestGenerator\Configuration\Configuration; +use PhpParser\Builder\Class_; +use PhpParser\Builder\Method; +use PhpParser\BuilderFactory; +use PhpParser\Node; +use PhpParser\PrettyPrinter; use ReflectionClass; -use Twig\Environment; -use Twig\Loader\FilesystemLoader; +use function array_map; +use function class_exists; +use function implode; +use function sprintf; use function str_replace; class TestClassGenerator @@ -19,15 +26,15 @@ class TestClassGenerator /** @var Inflector */ private $inflector; + /** @var BuilderFactory */ + private $builderFactory; + /** @var ReflectionClass */ private $reflectionClass; /** @var string */ private $classShortName; - /** @var string */ - private $classCamelCaseName; - /** @var string */ private $testNamespace; @@ -41,29 +48,22 @@ public function __construct( Configuration $configuration, ?Inflector $inflector = null ) { - $this->configuration = $configuration; - $this->inflector = $inflector ?? InflectorFactory::createEnglishInflector(); + $this->configuration = $configuration; + $this->inflector = $inflector ?? InflectorFactory::createEnglishInflector(); + $this->builderFactory = new BuilderFactory(); } public function generate(string $className) : GeneratedTestClass { - $this->reflectionClass = new ReflectionClass($className); - - $this->classShortName = $this->reflectionClass->getShortName(); - $this->classCamelCaseName = $this->inflector->camelize($this->classShortName); - $this->testNamespace = str_replace( - $this->configuration->getSourceNamespace(), - $this->configuration->getTestsNamespace(), - $this->reflectionClass->getNamespaceName() - ); - $this->testClassShortName = $this->classShortName . 'Test'; - $this->testClassName = $this->testNamespace . '\\' . $this->testClassShortName; + $this->init($className); $testClassMetadata = (new TestClassMetadataParser( $this->inflector ))->getTestClassMetadata($className); - $code = $this->renderTestClassCode($testClassMetadata); + $code = $this->generateTestClass($testClassMetadata); + + $code = $this->replaceWithNewLines($code); return new GeneratedTestClass( $className, @@ -72,23 +72,267 @@ public function generate(string $className) : GeneratedTestClass ); } - private function renderTestClassCode(TestClassMetadata $testClassMetadata) : string + private function replaceWithNewLines(string $code) : string + { + $code = str_replace(' private $__newLineReplace__;', '', $code); + + $code = str_replace(<<generateTestClassNodes($testClassMetadata); + + return (new PrettyPrinter\Standard()) + ->prettyPrintFile($nodes); + } + + /** + * @return Node[] + */ + private function generateTestClassNodes(TestClassMetadata $testClassMetadata) : array + { + $nodes = []; + + $nodes[] = new Node\Stmt\Declare_([new Node\Stmt\DeclareDeclare('strict_types', $this->builderFactory->val(1))]); + + $nodes[] = new Node\Stmt\Nop(); + + $namespaceBuilder = $this->builderFactory->namespace($this->testNamespace); + + foreach ($testClassMetadata->getUseStatements() as $useStatement) { + $namespaceBuilder->addStmt($this->builderFactory->use($useStatement)); + } + + $namespaceBuilder->addStmt(new Node\Stmt\Nop()); + + $classBuilder = $this->builderFactory->class($this->testClassShortName) + ->extend('TestCase'); + + $this->generateTestClassProperties($testClassMetadata, $classBuilder); + $this->generateTestClassTestMethods($testClassMetadata, $classBuilder); + $this->generateTestClassSetUpMethod($testClassMetadata, $classBuilder); + + $namespaceBuilder->addStmt($classBuilder); + + $nodes[] = $namespaceBuilder->getNode(); + + return $nodes; + } + + private function generateTestClassProperties( + TestClassMetadata $testClassMetadata, + Class_ $classBuilder + ) : void { + foreach ($testClassMetadata->getProperties() as $property) { + $classBuilder->addStmt( + $this->builderFactory->property($property['propertyName']) + ->makePrivate() + ->setDocComment($this->generatePropertyDocBlock($property)) + ); + + $classBuilder->addStmt( + $this->builderFactory->property('__newLineReplace__') + ->makePrivate() + ); + } + } + + /** + * @param string[] $property + */ + private function generatePropertyDocBlock(array $property) : string + { + $docBlockTypes = [$property['propertyType']]; + + if ($property['type'] === TestClassMetadataParser::DEPENDENCY) { + $docBlockTypes[] = 'MockObject'; + } + + return sprintf('/** @var %s */', implode('|', $docBlockTypes)); + } + + private function generateTestClassTestMethods( + TestClassMetadata $testClassMetadata, + Class_ $classBuilder + ) : void { + foreach ($testClassMetadata->getTestMethods() as $testMethod) { + $methodBuilder = $this->builderFactory->method($testMethod['methodName']) + ->makePublic() + ->setReturnType('void'); + + foreach ($testMethod['lines'] as $line) { + $this->generateTestClassMethodLine($methodBuilder, $line); + } + + $classBuilder->addStmt($methodBuilder); + + $classBuilder->addStmt($this->builderFactory->method('__newLineReplace__')); + } + } + + /** + * @param mixed[] $line + */ + private function generateTestClassMethodLine(Method $methodBuilder, array $line) : void { - return $this->createTwigEnvironment()->render('test-class.html.twig', [ - 'testClassMetadata' => $testClassMetadata, - 'classCamelCaseName' => $this->classCamelCaseName, - 'namespace' => $this->testNamespace, - 'shortName' => $this->testClassShortName, - ]); + switch ($line['type']) { + case TestClassMetadataParser::DEPENDENCY: + $methodBuilder->addStmt(new Node\Expr\Assign( + $this->builderFactory->var($line['variableName']), + $this->createMockMethodCall($line['variableType']) + )); + + break; + + case TestClassMetadataParser::NORMAL: + $methodBuilder->addStmt(new Node\Expr\Assign( + $this->builderFactory->var($line['variableName']), + $this->builderFactory->val('') + )); + + break; + + case TestClassMetadataParser::SUT: + $arguments = $this->builderFactory->args(array_map(function (string $parameter) { + return $this->builderFactory->var($parameter); + }, $line['arguments'])); + + $assertArguments = []; + $assertMethod = 'assertNull'; + + switch ($line['methodReturnType']) { + case 'null': + $assertMethod = 'assertNull'; + break; + + case 'string': + $assertMethod = 'assertSame'; + $assertArguments[] = ''; + break; + + case 'int': + $assertMethod = 'assertSame'; + $assertArguments[] = 1; + break; + + case 'float': + $assertMethod = 'assertSame'; + $assertArguments[] = 1.0; + break; + + case 'bool': + $assertMethod = 'assertTrue'; + break; + + case 'array': + $assertMethod = 'assertSame'; + $assertArguments[] = []; + break; + + default: + if (class_exists($line['methodReturnType'])) { + $reflectionClass = new ReflectionClass($line['methodReturnType']); + + $assertMethod = 'assertInstanceOf'; + $assertArguments[] = $this->builderFactory->classConstFetch( + $reflectionClass->getShortName(), + 'class' + ); + } + } + + $assertArguments[] = $this->builderFactory->methodCall( + $this->builderFactory->var('this->' . $line['variableName']), + $line['methodName'], + $arguments + ); + + $methodBuilder->addStmt( + $this->builderFactory->staticCall( + 'self', + $assertMethod, + $assertArguments + ) + ); + + break; + } } - private function createTwigEnvironment() : Environment + private function generateTestClassSetUpMethod( + TestClassMetadata $testClassMetadata, + Class_ $classBuilder + ) : void { + $methodBuilder = $this->builderFactory->method('setUp') + ->makeProtected() + ->setReturnType('void'); + + foreach ($testClassMetadata->getSetUpDependencies() as $setUpDependency) { + switch ($setUpDependency['type']) { + case TestClassMetadataParser::DEPENDENCY: + $methodBuilder->addStmt(new Node\Expr\Assign( + $this->builderFactory->var('this->' . $setUpDependency['propertyName']), + $this->createMockMethodCall($setUpDependency['propertyType']) + )); + + break; + + case TestClassMetadataParser::NORMAL: + $methodBuilder->addStmt(new Node\Expr\Assign( + $this->builderFactory->var('this->' . $setUpDependency['propertyName']), + $this->builderFactory->val($setUpDependency['propertyValue']) + )); + + break; + + case TestClassMetadataParser::SUT: + $arguments = array_map(function (string $argument) { + return $this->builderFactory->var('this->' . $argument); + }, $setUpDependency['arguments']); + + $methodBuilder->addStmt(new Node\Expr\Assign( + $this->builderFactory->var('this->' . $setUpDependency['propertyName']), + $this->builderFactory->new( + new Node\Name($setUpDependency['propertyType']), + $arguments + ) + )); + + break; + } + } + + $classBuilder->addStmt($methodBuilder); + } + + private function createMockMethodCall(string $className) : Node\Expr\MethodCall + { + return $this->builderFactory->methodCall( + $this->builderFactory->var('this'), + 'createMock', + [$this->builderFactory->classConstFetch($className, 'class')] + ); + } + + private function init(string $className) : void { - $loader = new FilesystemLoader([__DIR__ . '/Templates']); + $this->reflectionClass = new ReflectionClass($className); - return new Environment($loader, [ - 'strict_variables' => true, - 'autoescape' => false, - ]); + $this->classShortName = $this->reflectionClass->getShortName(); + $this->testNamespace = str_replace( + $this->configuration->getSourceNamespace(), + $this->configuration->getTestsNamespace(), + $this->reflectionClass->getNamespaceName() + ); + $this->testClassShortName = $this->classShortName . 'Test'; + $this->testClassName = $this->testNamespace . '\\' . $this->testClassShortName; } } diff --git a/lib/TestClassMetadataParser.php b/lib/TestClassMetadataParser.php index 66628b0..daab3fd 100644 --- a/lib/TestClassMetadataParser.php +++ b/lib/TestClassMetadataParser.php @@ -12,13 +12,19 @@ use ReflectionParameter; use function array_map; use function array_unique; +use function class_exists; use function count; use function sort; +use function sprintf; use function substr; -use function var_export; +use function ucfirst; class TestClassMetadataParser { + public const DEPENDENCY = 'dependency'; + public const NORMAL = 'normal'; + public const SUT = 'sut'; + /** @var Inflector */ private $inflector; @@ -46,7 +52,7 @@ public function getTestClassMetadata(string $className) : TestClassMetadata return new TestClassMetadata( $this->generateUseStatements(), $this->generateClassProperties(), - $this->generateSetUpDependencies(), + $this->generateSetUpLines(), $this->generateTestMethods() ); } @@ -56,9 +62,9 @@ public function getTestClassMetadata(string $className) : TestClassMetadata */ private function generateUseStatements() : array { - $dependencies = []; - $dependencies[] = $this->reflectionClass->name; - $dependencies[] = TestCase::class; + $useStatements = []; + $useStatements[] = $this->reflectionClass->name; + $useStatements[] = TestCase::class; $parameters = $this->getConstructorParameters(); @@ -70,7 +76,7 @@ private function generateUseStatements() : array continue; } - $dependencies[] = $parameterClass->getName(); + $useStatements[] = $parameterClass->getName(); } } @@ -86,17 +92,29 @@ private function generateUseStatements() : array continue; } - $dependencies[] = $parameterClass->getName(); + $useStatements[] = $parameterClass->getName(); + } + + $returnType = $method->getReturnType(); + + if ($returnType !== null) { + $returnTypeName = $returnType->getName(); + + if (! class_exists($returnTypeName)) { + continue; + } } + + $useStatements[] = $returnType; } - $dependencies[] = MockObject::class; + $useStatements[] = MockObject::class; - sort($dependencies); + $useStatements = array_unique($useStatements); - $dependencies = array_unique($dependencies); + sort($useStatements); - return $dependencies; + return $useStatements; } /** @@ -104,7 +122,7 @@ private function generateUseStatements() : array */ private function generateClassProperties() : array { - $testProperties = []; + $classProperties = []; $parameters = $this->getConstructorParameters(); @@ -112,80 +130,71 @@ private function generateClassProperties() : array $parameterClass = $parameter->getClass(); if ($parameterClass !== null) { - $testProperties[] = [ - 'type' => 'dependency', + $classProperties[] = [ + 'type' => self::DEPENDENCY, 'propertyType' => $parameterClass->getShortName(), 'propertyName' => $parameter->name, ]; } else { - $testProperties[] = [ - 'type' => 'normal', + $classProperties[] = [ + 'type' => self::NORMAL, 'propertyType' => (string) $parameter->getType(), 'propertyName' => $parameter->name, ]; } } - $testProperties[] = [ - 'type' => 'normal', + $classProperties[] = [ + 'type' => self::NORMAL, 'propertyType' => $this->classShortName, 'propertyName' => $this->classCamelCaseName, ]; - return $testProperties; + return $classProperties; } /** * @return mixed[] */ - private function generateSetUpDependencies() : array + private function generateSetUpLines() : array { $classShortName = $this->reflectionClass->getShortName(); $classCamelCaseName = $this->inflector->camelize($classShortName); - $setUpDependencies = []; + $setUpLines = []; $parameters = $this->getConstructorParameters(); - if (count($parameters) !== 0) { - foreach ($parameters as $parameter) { - $parameterClass = $parameter->getClass(); + foreach ($parameters as $parameter) { + $parameterClass = $parameter->getClass(); - if ($parameterClass !== null) { - $setUpDependencies[] = [ - 'type' => 'dependency', - 'propertyName' => $parameter->name, - 'propertyType' => $parameterClass->getShortName(), - ]; - } else { - $typeRandomValue = $this->generateTypeRandomValue((string) $parameter->getType()); - - $setUpDependencies[] = [ - 'type' => 'normal', - 'propertyName' => $parameter->name, - 'propertyValue' => var_export($typeRandomValue, true), - ]; - } - } + if ($parameterClass !== null) { + $setUpLines[] = [ + 'type' => self::DEPENDENCY, + 'propertyName' => $parameter->name, + 'propertyType' => $parameterClass->getShortName(), + ]; + } else { + $typeRandomValue = $this->generateTypeRandomValue((string) $parameter->getType()); - $setUpDependencies[] = [ - 'type' => 'sut', - 'propertyName' => $classCamelCaseName, - 'propertyType' => $classShortName, - 'parameters' => array_map(static function (ReflectionParameter $parameter) { - return $parameter->name; - }, $parameters), - ]; - } else { - $setUpDependencies[] = [ - 'type' => 'sut', - 'propertyName' => $classCamelCaseName, - 'propertyType' => $classShortName, - 'parameters' => [], - ]; + $setUpLines[] = [ + 'type' => self::NORMAL, + 'propertyName' => $parameter->name, + 'propertyValue' => $typeRandomValue, + ]; + } } - return $setUpDependencies; + $setUpLines[] = [ + 'type' => self::SUT, + 'propertyName' => $classCamelCaseName, + 'propertyType' => $classShortName, + 'arguments' => array_map(static function (ReflectionParameter $parameter) { + return $parameter->name; + }, $parameters), + ]; + + return $setUpLines; } /** @@ -215,8 +224,8 @@ private function generateTestMethods() : array } $testMethods[] = [ - 'methodName' => $method->name, - 'body' => $this->generateTestMethodBody($method), + 'methodName' => sprintf('test%s', ucfirst($method->name)), + 'lines' => $this->generateTestMethodLines($method), ]; } @@ -226,48 +235,47 @@ private function generateTestMethods() : array /** * @return mixed[] */ - private function generateTestMethodBody(ReflectionMethod $method) : array + private function generateTestMethodLines(ReflectionMethod $method) : array { $parameters = $method->getParameters(); - $testMethodBody = []; + $testMethodLines = []; - if (count($parameters) !== 0) { - foreach ($parameters as $parameter) { - $parameterClass = $parameter->getClass(); + foreach ($parameters as $parameter) { + $parameterClass = $parameter->getClass(); - if ($parameterClass !== null) { - $testMethodBody[] = [ - 'type' => 'dependency', - 'parameterName' => $parameter->name, - 'parameterType' => $parameterClass->getShortName(), - ]; - } else { - $testMethodBody[] = [ - 'type' => 'normal', - 'parameterName' => $parameter->name, - ]; - } + if ($parameterClass !== null) { + $testMethodLines[] = [ + 'type' => self::DEPENDENCY, + 'variableName' => $parameter->name, + 'variableType' => $parameterClass->getShortName(), + ]; + } else { + $testMethodLines[] = [ + 'type' => self::NORMAL, + 'variableName' => $parameter->name, + ]; } + } - $testMethodBody[] = [ - 'type' => 'sut', - 'parameterName' => $this->classCamelCaseName, - 'methodName' => $method->name, - 'parameters' => array_map(static function (ReflectionParameter $parameter) { - return $parameter->name; - }, $parameters), - ]; - } else { - $testMethodBody[] = [ - 'type' => 'sut', - 'parameterName' => $this->classCamelCaseName, - 'methodName' => $method->name, - 'parameters' => [], - ]; + $returnType = $method->getReturnType(); + $returnTypeName = ''; + + if ($returnType !== null) { + $returnTypeName = $returnType->getName(); } - return $testMethodBody; + $testMethodLines[] = [ + 'type' => self::SUT, + 'variableName' => $this->classCamelCaseName, + 'methodName' => $method->name, + 'methodReturnType' => $returnTypeName, + 'arguments' => array_map(static function (ReflectionParameter $parameter) { + return $parameter->name; + }, $parameters), + ]; + + return $testMethodLines; } private function isMethodTestable(ReflectionMethod $method) : bool @@ -285,14 +293,20 @@ private function isMethodTestable(ReflectionMethod $method) : bool private function generateTypeRandomValue(string $type) { switch ($type) { - case 'string': - return ''; + case 'array': + return []; + + case 'bool': + return true; case 'float': return 1.0; case 'int': return 1; + + case 'string': + return ''; } return ''; diff --git a/lib/Writer/Psr4TestClassWriter.php b/lib/Writer/Psr4TestClassWriter.php index 277e8f0..2efaca7 100644 --- a/lib/Writer/Psr4TestClassWriter.php +++ b/lib/Writer/Psr4TestClassWriter.php @@ -7,12 +7,9 @@ use JWage\PHPUnitTestGenerator\Configuration\Configuration; use JWage\PHPUnitTestGenerator\GeneratedTestClass; use RuntimeException; +use Symfony\Component\Filesystem\Filesystem; use const DIRECTORY_SEPARATOR; use function dirname; -use function file_exists; -use function file_put_contents; -use function is_dir; -use function mkdir; use function sprintf; use function str_replace; @@ -21,9 +18,13 @@ class Psr4TestClassWriter implements TestClassWriter /** @var Configuration */ private $configuration; - public function __construct(Configuration $configuration) + /** @var Filesystem */ + private $filesystem; + + public function __construct(Configuration $configuration, ?Filesystem $filesystem = null) { $this->configuration = $configuration; + $this->filesystem = $filesystem ?? new Filesystem(); } public function write(GeneratedTestClass $generatedTestClass) : string @@ -32,15 +33,15 @@ public function write(GeneratedTestClass $generatedTestClass) : string $writeDirectory = dirname($writePath); - if (! is_dir($writeDirectory)) { - mkdir($writeDirectory, 0777, true); + if (! $this->filesystem->exists($writeDirectory)) { + $this->filesystem->mkdir($writeDirectory, 0777); } - if (file_exists($writePath)) { + if ($this->filesystem->exists($writePath)) { throw new RuntimeException(sprintf('Test class already exists at %s', $writePath)); } - file_put_contents( + $this->filesystem->dumpFile( $writePath, $generatedTestClass->getCode() ); diff --git a/tests/Configuration/ComposerConfigurationReaderTest.php b/tests/Configuration/ComposerConfigurationReaderTest.php new file mode 100644 index 0000000..a4d6461 --- /dev/null +++ b/tests/Configuration/ComposerConfigurationReaderTest.php @@ -0,0 +1,37 @@ +composerConfigurationReader->createConfiguration($rootDir); + + self::assertSame(AutoloadingStrategy::PSR4, $configuration->getAutoloadingStrategy()); + self::assertSame('JWage\PHPUnitTestGenerator', $configuration->getSourceNamespace()); + self::assertSame(realpath(__DIR__ . '/../../lib'), $configuration->getSourceDir()); + self::assertSame('JWage\PHPUnitTestGenerator\Tests', $configuration->getTestsNamespace()); + self::assertSame(realpath(__DIR__ . '/../../tests'), $configuration->getTestsDir()); + } + + protected function setUp() : void + { + $this->composerConfigurationReader = new ComposerConfigurationReader(); + } +} diff --git a/tests/Configuration/ConfigurationBuilderTest.php b/tests/Configuration/ConfigurationBuilderTest.php new file mode 100644 index 0000000..eb5d260 --- /dev/null +++ b/tests/Configuration/ConfigurationBuilderTest.php @@ -0,0 +1,75 @@ +configurationBuilder->setAutoloadingStrategy($autoloadingStrategy); + + $configuration = $this->configurationBuilder->build(); + + self::assertSame($autoloadingStrategy, $configuration->getAutoloadingStrategy()); + } + + public function testSetSourceNamespace() : void + { + $sourceNamespace = 'App'; + + $this->configurationBuilder->setSourceNamespace($sourceNamespace); + + $configuration = $this->configurationBuilder->build(); + + self::assertSame($sourceNamespace, $configuration->getSourceNamespace()); + } + + public function testSetSourceDir() : void + { + $sourceDir = '/source/dir'; + + $this->configurationBuilder->setSourceDir($sourceDir); + + $configuration = $this->configurationBuilder->build(); + + self::assertSame($sourceDir, $configuration->getSourceDir()); + } + + public function testSetTestsNamespace() : void + { + $testsNamespace = 'App\Tests'; + + $this->configurationBuilder->setTestsNamespace($testsNamespace); + + $configuration = $this->configurationBuilder->build(); + + self::assertSame($testsNamespace, $configuration->getTestsNamespace()); + } + + public function testSetTestsDir() : void + { + $testsDir = '/tests/dir'; + + $this->configurationBuilder->setTestsDir($testsDir); + + $configuration = $this->configurationBuilder->build(); + + self::assertSame($testsDir, $configuration->getTestsDir()); + } + + protected function setUp() : void + { + $this->configurationBuilder = new ConfigurationBuilder(); + } +} diff --git a/tests/GeneratedTestClassTest.php b/tests/GeneratedTestClassTest.php new file mode 100644 index 0000000..70649f0 --- /dev/null +++ b/tests/GeneratedTestClassTest.php @@ -0,0 +1,51 @@ +className, $this->generatedTestClass->getClassName()); + } + + public function testGetTestClassName() : void + { + self::assertSame($this->testClassName, $this->generatedTestClass->getTestClassName()); + } + + public function testGetCode() : void + { + self::assertSame($this->code, $this->generatedTestClass->getCode()); + } + + protected function setUp() : void + { + $this->className = 'App\User'; + $this->testClassName = 'App\Tests\User'; + $this->code = 'generatedTestClass = new GeneratedTestClass( + $this->className, + $this->testClassName, + $this->code + ); + } +} diff --git a/tests/InflectorFactoryTest.php b/tests/InflectorFactoryTest.php new file mode 100644 index 0000000..906e427 --- /dev/null +++ b/tests/InflectorFactoryTest.php @@ -0,0 +1,18 @@ +singularize('apples')); + } +} diff --git a/tests/TestClass.php b/tests/TestClass1.php similarity index 61% rename from tests/TestClass.php rename to tests/TestClass1.php index e986c55..de7e7a7 100644 --- a/tests/TestClass.php +++ b/tests/TestClass1.php @@ -4,7 +4,7 @@ namespace JWage\PHPUnitTestGenerator\Tests; -class TestClass +class TestClass1 { /** @var TestDependency */ private $testDependency; @@ -18,16 +18,24 @@ class TestClass /** @var string */ private $testStringArgument; + /** @var mixed[] */ + private $testArrayArgument; + + /** + * @param mixed[] $testArrayArgument + */ public function __construct( TestDependency $testDependency, float $testFloatArgument, int $testIntegerArgument, - string $testStringArgument + string $testStringArgument, + array $testArrayArgument ) { $this->testDependency = $testDependency; $this->testFloatArgument = $testFloatArgument; $this->testIntegerArgument = $testIntegerArgument; $this->testStringArgument = $testStringArgument; + $this->testArrayArgument = $testArrayArgument; } public function getTestDependency() : TestDependency @@ -35,12 +43,17 @@ public function getTestDependency() : TestDependency return $this->testDependency; } + public function setTestDependency(TestDependency $testDependency) : void + { + $this->testDependency = $testDependency; + } + public function getTestFloatArgument() : float { return $this->testFloatArgument; } - public function getTestIntegerArgument() : float + public function getTestIntegerArgument() : int { return $this->testIntegerArgument; } @@ -50,6 +63,14 @@ public function getTestStringArgument() : string return $this->testStringArgument; } + /** + * @return mixed[] + */ + public function getTestArrayArgument() : array + { + return $this->testArrayArgument; + } + public function getTestMethodWithArguments(string $a, float $b, int $c) : void { } @@ -58,4 +79,17 @@ public function getSomething() : string { return 'something'; } + + public function getTestBoolean() : bool + { + return true; + } + + /** + * @return mixed[] + */ + public function getTestArray() : array + { + return []; + } } diff --git a/tests/TestClass2.php b/tests/TestClass2.php new file mode 100644 index 0000000..8685368 --- /dev/null +++ b/tests/TestClass2.php @@ -0,0 +1,12 @@ +testClass->getTestDependency(); + self::assertInstanceOf(TestDependency::class, $this->testClass1->getTestDependency()); + } + + public function testSetTestDependency() : void + { + $testDependency = $this->createMock(TestDependency::class); + self::assertNull($this->testClass1->setTestDependency($testDependency)); } public function testGetTestFloatArgument() : void { - $this->testClass->getTestFloatArgument(); + self::assertSame(1.0, $this->testClass1->getTestFloatArgument()); } public function testGetTestIntegerArgument() : void { - $this->testClass->getTestIntegerArgument(); + self::assertSame(1, $this->testClass1->getTestIntegerArgument()); } public function testGetTestStringArgument() : void { - $this->testClass->getTestStringArgument(); + self::assertSame('', $this->testClass1->getTestStringArgument()); + } + + public function testGetTestArrayArgument() : void + { + self::assertSame(array(), $this->testClass1->getTestArrayArgument()); } public function testGetTestMethodWithArguments() : void @@ -64,17 +78,22 @@ public function testGetTestMethodWithArguments() : void $a = ''; $b = ''; $c = ''; - $this->testClass->getTestMethodWithArguments( - $a, - $b, - $c - ); - + self::assertNull($this->testClass1->getTestMethodWithArguments($a, $b, $c)); } public function testGetSomething() : void { - $this->testClass->getSomething(); + self::assertSame('', $this->testClass1->getSomething()); + } + + public function testGetTestBoolean() : void + { + self::assertTrue($this->testClass1->getTestBoolean()); + } + + public function testGetTestArray() : void + { + self::assertSame(array(), $this->testClass1->getTestArray()); } protected function setUp() : void @@ -83,26 +102,64 @@ protected function setUp() : void $this->testFloatArgument = 1.0; $this->testIntegerArgument = 1; $this->testStringArgument = ''; + $this->testArrayArgument = array(); + $this->testClass1 = new TestClass1($this->testDependency, $this->testFloatArgument, $this->testIntegerArgument, $this->testStringArgument, $this->testArrayArgument); + } +} + +EOF; - $this->testClass = new TestClass( - $this->testDependency, - $this->testFloatArgument, - $this->testIntegerArgument, - $this->testStringArgument - ); + private const EXPECTED_TEST_CLASS2 = <<<'EOF' +testClass2->getSomething()); + } + + protected function setUp() : void + { + $this->testClass2 = new TestClass2(); } } EOF; - public function testGenerate() : void + /** + * @dataProvider getTestClasses + */ + public function testGenerate(string $class, string $expected) : void { $configuration = (new ConfigurationBuilder())->build(); $testClassGenerator = new TestClassGenerator($configuration); - $generatedTestClass = $testClassGenerator->generate(TestClass::class); + $generatedTestClass = $testClassGenerator->generate($class); + + self::assertSame($expected, $generatedTestClass->getCode()); + } - self::assertSame(self::EXPECTED_GENERATED_TEST_CLASS, $generatedTestClass->getCode()); + /** + * @return string[][] + */ + public function getTestClasses() : array + { + return [ + [TestClass1::class, self::EXPECTED_TEST_CLASS1], + [TestClass2::class, self::EXPECTED_TEST_CLASS2], + ]; } } diff --git a/tests/TestClassMetadataTest.php b/tests/TestClassMetadataTest.php new file mode 100644 index 0000000..3aa40b2 --- /dev/null +++ b/tests/TestClassMetadataTest.php @@ -0,0 +1,61 @@ +useStatements, $this->testClassMetadata->getUseStatements()); + } + + public function testGetProperties() : void + { + self::assertSame($this->properties, $this->testClassMetadata->getProperties()); + } + + public function testGetSetUpDependencies() : void + { + self::assertSame($this->setUpDependencies, $this->testClassMetadata->getSetUpDependencies()); + } + + public function testGetTestMethods() : void + { + self::assertSame($this->testMethods, $this->testClassMetadata->getTestMethods()); + } + + protected function setUp() : void + { + $this->useStatements = [self::class]; + $this->properties = ['property']; + $this->setUpDependencies = ['setUpDependency']; + $this->testMethods = ['testMethod']; + + $this->testClassMetadata = new TestClassMetadata( + $this->useStatements, + $this->properties, + $this->setUpDependencies, + $this->testMethods + ); + } +} diff --git a/tests/Writer/Psr4TestClassWriterTest.php b/tests/Writer/Psr4TestClassWriterTest.php new file mode 100644 index 0000000..e7a9ef4 --- /dev/null +++ b/tests/Writer/Psr4TestClassWriterTest.php @@ -0,0 +1,108 @@ +filesystem->expects(self::at(0)) + ->method('exists') + ->with('/data/tests') + ->willReturn(false); + + $this->filesystem->expects(self::once()) + ->method('mkdir') + ->with('/data/tests'); + + $this->filesystem->expects(self::at(2)) + ->method('exists') + ->with('/data/tests/UserTest.php') + ->willReturn(false); + + $this->filesystem->expects(self::at(3)) + ->method('dumpFile') + ->with('/data/tests/UserTest.php', 'psr4TestClassWriter->write($generatedTestClass); + + self::assertSame('/data/tests/UserTest.php', $writePath); + } + + + public function testWriteTestClassAlreadyExists() : void + { + $generatedTestClass = new GeneratedTestClass( + 'App\User', + 'App\Tests\UserTest', + 'filesystem->expects(self::at(0)) + ->method('exists') + ->with('/data/tests') + ->willReturn(false); + + $this->filesystem->expects(self::once()) + ->method('mkdir') + ->with('/data/tests'); + + $this->filesystem->expects(self::at(2)) + ->method('exists') + ->with('/data/tests/UserTest.php') + ->willReturn(true); + + $this->filesystem->expects(self::never()) + ->method('dumpFile'); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Test class already exists at /data/tests/UserTest.php'); + + $this->psr4TestClassWriter->write($generatedTestClass); + } + + protected function setUp() : void + { + $this->configuration = (new ConfigurationBuilder()) + ->setAutoloadingStrategy(AutoloadingStrategy::PSR4) + ->setSourceNamespace('App') + ->setSourceDir('/data/lib') + ->setTestsNamespace('App\Tests') + ->setTestsDir('/data/tests') + ->build(); + + $this->filesystem = $this->createMock(Filesystem::class); + + $this->psr4TestClassWriter = new Psr4TestClassWriter( + $this->configuration, + $this->filesystem + ); + } +}