├── .editorconfig
├── .gitignore
├── .travis.yml
├── CHANGELOG.md
├── LICENSE
├── Makefile
├── README.md
├── bin
└── test-generator
├── box.json.dist
├── composer.json
├── composer.lock
├── doc
├── phpstorm-integration.png
└── testgenerator-logo.png
├── infection.json.dist
├── phpstan.neon
├── phpunit.xml.dist
├── src
├── ClassAnalyser.php
├── Clazz.php
├── Dependency.php
├── DependencyContainer.php
├── InvalidFullyQualifiedNameException.php
├── NotAPhpFileException.php
├── Output
│ ├── Exception
│ │ ├── InvalidFileException.php
│ │ ├── SubjectNotInSrcBaseException.php
│ │ └── UnableToWriteTestFileException.php
│ ├── FileWriter.php
│ ├── OutputProcessor.php
│ ├── OutputProcessorFactory.php
│ └── StdoutWriter.php
├── PhpFile.php
├── TemplateConfiguration.php
├── TestGenerator.php
└── TwigRenderer.php
├── templates
└── phpunit.twig
├── tests
├── functional
│ ├── FunctionalBaseTest.php
│ ├── backlog
│ │ ├── AliasFlag
│ │ │ ├── arguments.txt
│ │ │ ├── expected.php
│ │ │ └── source.php
│ │ ├── MultipleClassesPerFile
│ │ │ ├── expected.php
│ │ │ ├── expected2.php
│ │ │ └── source.php
│ │ └── Php5SupportWithMockery
│ │ │ ├── expected.php
│ │ │ └── source.php
│ └── fixtures
│ │ ├── AddsCoversAnnotation
│ │ ├── arguments.txt
│ │ ├── expected.php
│ │ └── source.php
│ │ ├── ClassWithScalarValues
│ │ ├── expected.php
│ │ └── source.php
│ │ ├── DifferentBaseClass
│ │ ├── arguments.txt
│ │ ├── expected.php
│ │ └── source.php
│ │ ├── FormattingForFieldsAndTestSubject
│ │ ├── arguments.txt
│ │ ├── expected.php
│ │ └── source.php
│ │ ├── GeneratesSimplePhpUnitTestCase
│ │ ├── expected.php
│ │ └── source.php
│ │ ├── MockeryMocking
│ │ ├── arguments.txt
│ │ ├── expected.php
│ │ └── source.php
│ │ └── Php5SupportWithPhpUnit5
│ │ ├── arguments.txt
│ │ ├── expected.php
│ │ └── source.php
├── integration
│ ├── PhpBackportingTest.php
│ └── TemplateTest.php
├── resources
│ ├── php7-to-php55-example
│ │ ├── example.php
│ │ └── expected.php
│ └── simple-example-project
│ │ └── src
│ │ └── Deep
│ │ └── X.php
└── unit
│ ├── ClassAnalyserTest.php
│ ├── ClazzTest.php
│ ├── DependencyContainerTest.php
│ ├── DependencyTest.php
│ ├── Output
│ ├── FileWriterTest.php
│ ├── OutputProcessorFactoryTest.php
│ └── StdoutWriterTest.php
│ ├── PhpFileTest.php
│ ├── TemplateConfigurationTest.php
│ ├── TestGeneratorTest.php
│ └── TwigRendererTest.php
└── tools
├── ensure-docopt-readme-and-app-are-identical
├── githook
└── remove-php7-features
/.editorconfig:
--------------------------------------------------------------------------------
1 | # This file is for unifying the coding style for different editors and IDEs
2 | # editorconfig.org
3 |
4 | # PHP PSR-2 Coding Standards
5 | # http://www.php-fig.org/psr/psr-2/
6 |
7 | root = true
8 |
9 | [*.php]
10 | charset = utf-8
11 | end_of_line = lf
12 | insert_final_newline = true
13 | trim_trailing_whitespace = true
14 | indent_style = space
15 | indent_size = 4
16 |
17 | [Makefile]
18 | indent_style = tab
19 |
20 | # Matches the exact files either package.json or .travis.yml
21 | [{package.json,.travis.yml}]
22 | indent_style = space
23 | indent_size = 2
24 |
25 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # PHPStorm settings, etc.
2 | .idea
3 |
4 | # composer dependencies
5 | /vendor
6 | /samples
7 |
8 | # PHP Coding Standards Fixer cache file
9 | .php_cs.cache
10 |
11 | # for now don't add any more .phar files
12 | # because they might bloat the repo
13 | *.phar
14 |
15 | # build artifacts
16 | /build
17 | humbuglog.txt
18 |
19 | tests/resources/simple-example-project/tests
20 | tests/resources/php7-to-php55-example/actual.php
21 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 |
3 | php:
4 | - 7.1
5 | - 7.2
6 | - 7.3
7 | - nightly
8 |
9 | matrix:
10 | allow_failures:
11 | - php: nightly
12 |
13 | before_script:
14 | - travis_retry composer self-update
15 | - travis_retry composer update --no-interaction --prefer-source
16 |
17 | script:
18 | - vendor/bin/phpunit --coverage-clover=coverage.xml
19 | - vendor/bin/infection
20 | - tools/ensure-docopt-readme-and-app-are-identical
21 |
22 | after_success:
23 | - bash <(curl -s https://codecov.io/bash)
24 |
25 | env:
26 | global:
27 | secure: R3XOmZ+j5oCsqfYmJQcRQ+LY9x70Ht3MX3V8jd5Ze4ZqRAeod6ZdlAvk5ae8IcvhIUC/gfUZCEGDa+EWXhMRxNf4RPszK9D8Gcz/TxPT3lkZVg+zdz/Fa2vNtthCQGc+HqZU2dnWtK4RV6TH0yq0ra5ychG5fOXEKMfk28WdznPMqgQwdPthjKX6tihVtT6M5QgR40h2daN5sm7OGUPP4iorrwsAhceGzz2OZeMib30oWpblIFPBJ1Ibf7Q73+T8w8tdhA4zMYERQ4DLZUr5+V90GQNu6NZ4y2IJmDW1jKYKJS6VsfLuLXi8GoEik8SjIrgH0tzaFBQUCZej77HqphUdtasRAhRzG564gLjmWMAeuRQyiA0m8cltnCIGj+V5EEiY1D1weyTMrHa5+kEubi8RkzUHAKyxd2XTRTSPMIiJW/jzhyVEv5MpD9Ma44fu49zdbex8en3MgNvAMoH66YK79gULuU1+RDl7ulgAdlHehEZM2AQeYk37seKcAcZPWtFO47jVgR7K+dMohUxwLEGFLCkkvWSFh2nVKUiNMyeVe5Dcs6316W2dNTgGHfjWneUE3lK2gyEwAfrC3e9dxv+FytlxI+Tf8PIo5XSGYf0PkbipFT7BPe60fehFIIcxCYNteDXW93uG6g9fN+etstmWimKnKOEyRNYTCLCOsVE=
28 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 | All notable changes to this project will be documented in this file.
3 |
4 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
5 | and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
6 |
7 | ## [Unreleased]
8 |
9 | ## [1.3.0] - 2018-08-25
10 | ### Added
11 | - Add `--src-base` (`-s`) and `--test-base` (`-t`) which can be used for saving the test file instead of printing to stdout
12 |
13 | ## [1.2.0] - 2017-08-28
14 | ### Added
15 | - Add formatting options via `--base-class`, `--subject-format` and `--field-format`
16 |
17 | ### Fixed
18 | - Fix style issue with classes without dependencies
19 |
20 | ## [1.1.0] - 2017-08-27
21 | ### Added
22 | - Support for namespaces, PHP5, PHPUnit5 and Mockery
23 |
24 | ## [1.0.0] - 2017-08-27
25 | ### Added
26 | - Generate PHPUnit 6 tests using PHPUnit for mocking
27 | - Backport to PHP5 via bin/remove-php7-features
28 |
29 | [Unreleased]: https://github.com/mihaeu/php-test-generator/compare/1.3.0...HEAD
30 | [1.3.0]: https://github.com/mihaeu/php-test-generator/compare/1.2.0...1.3.0
31 | [1.2.0]: https://github.com/mihaeu/php-test-generator/compare/1.1.0...1.2.0
32 | [1.1.0]: https://github.com/mihaeu/php-test-generator/compare/1.0.0...1.1.0
33 | [1.0.0]: https://github.com/mihaeu/php-test-generator/compare/0e8be99...1.0.0
34 |
35 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2017-2018 Michael Haeuslmann
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
8 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | NO_COLOR=\x1b[0m
2 | OK_COLOR=\x1b[32;01m
3 | ERROR_COLOR=\x1b[31;01m
4 | WARN_COLOR=\x1b[33;01m
5 |
6 | PHP=php
7 | PHP_NO_INI=php -n
8 | PHPUNIT=vendor/bin/phpunit
9 |
10 | all: check-reqs checks autoload test testdox cov humbug
11 |
12 | autoload:
13 | composer install
14 |
15 | checks: phpstan phpcs
16 |
17 | check-reqs:
18 | @echo "Verifying dev dependencies are installed ..."
19 | @test -f box.phar || { echo >&2 "Box is not installed locally"; exit 1; }
20 | @echo Ok.
21 |
22 | cov:
23 | @$(PHP) $(PHPUNIT) -c phpunit.xml.dist --coverage-text
24 |
25 | feature:
26 | @$(PHP_NO_INI) $(PHPUNIT) -c phpunit.xml.dist --testsuite=functional --testdox\
27 | | sed 's/\[x\]/$(OK_COLOR)$\[x]$(NO_COLOR)/' \
28 | | sed -r 's/(\[ \].+)/$(ERROR_COLOR)\1$(NO_COLOR)/' \
29 | | sed -r 's/(^[^ ].+)/$(WARN_COLOR)\1$(NO_COLOR)/'
30 |
31 | humbug:
32 | @vendor/bin/humbug
33 |
34 | unit:
35 | $(PHP_NO_INI) $(PHPUNIT) -c phpunit.xml.dist --testsuite=unit
36 |
37 | integration:
38 | $(PHP_NO_INI) $(PHPUNIT) -c phpunit.xml.dist --testsuite=integration
39 |
40 | test: unit feature integration
41 |
42 | testdox:
43 | @$(PHP_NO_INI) $(PHPUNIT) -c phpunit.xml.dist --testdox \
44 | | sed 's/\[x\]/$(OK_COLOR)$\[x]$(NO_COLOR)/' \
45 | | sed -r 's/(\[ \].+)/$(ERROR_COLOR)\1$(NO_COLOR)/' \
46 | | sed -r 's/(^[^ ].+)/$(WARN_COLOR)\1$(NO_COLOR)/'
47 |
48 | backport:
49 | find src -iname '*.php' -exec tools/remove-php7-features --write {} \;
50 |
51 | phar:
52 | @composer install --no-dev
53 | @mkdir -p build
54 | @$(PHP) box.phar build
55 | @chmod +x build/test-generator.phar
56 | @composer install
57 |
58 | phar55: backport phar
59 | git checkout -- src
60 |
61 | phpstan:
62 | @$(PHP_NO_INI) vendor/bin/phpstan analyse src tests/unit --level=4 -c phpstan.neon
63 |
64 | phpmd:
65 | @$(PHP_NO_INI) vendor/bin/phpmd src,tests/unit text cleancode,codesize,controversial,design,naming,unusedcode
66 |
67 | phpcs:
68 | @$(PHP_NO_INI) vendor/bin/phpcs --standard=PSR2 src tests/unit
69 |
70 | phpcbf:
71 | @$(PHP_NO_INI) vendor/bin/phpcbf --standard=PSR2 src tests/unit
72 |
73 | c: cov
74 |
75 | d: testdox
76 |
77 | s: style
78 |
79 | t: test
80 |
81 | f: feature
82 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | [](https://travis-ci.org/mihaeu/php-test-generator)
6 | [](https://codecov.io/gh/mihaeu/php-test-generator)
7 | [](https://infection.github.io)
8 | 
9 | 
10 | 
11 | 
12 | 
13 | 
14 |
15 | > Generate test cases for existing files
16 |
17 | ## Use Cases
18 |
19 | - PHPStorm has Apache Velocity support for file templates, but it is annoying to work with and limited
20 | - other IDEs or editors like Vim or Emacs don't have built-in code generation
21 | - somehow the test files never end up where they belong forcing you to rearrange code manually
22 |
23 | `test-generator` saves you all the tedious work of typing repetitive code when testing legacy applications. Next time you write a test for a class with too many dependencies and you start mocking away think of how much time you could've saved if you could automate this.
24 |
25 | This is where `test-generator` comes into play. Try it out, configure everything to your needs and create an alias for your shell or even better include it as an external tool in your editor/IDE ([like PHPStorm](https://www.jetbrains.com/help/phpstorm/external-tools.html)).
26 |
27 | ## Usage
28 |
29 | ### CLI
30 |
31 | ```bash
32 | bin/test-generator --help
33 | ```
34 |
35 | ```
36 | Test-Generator
37 |
38 | Usage:
39 | test-generator [options] [(--src-base --test-base)]
40 |
41 | Options:
42 | --php5 Generate PHP5 compatible code [default:false].
43 | --phpunit5 Generate a test for PHPUnit 5 [default:false].
44 | --mockery Generates mocks using Mockery [default:false].
45 | --covers Adds the @covers annotation [default:false].
46 | --base-class= Inherit from this base class e.g. "Example\TestCase".
47 | --subject-format= Format the field for the subject class.
48 | --field-format= Format the fields for dependencies.
49 | -s, --src-base= Base directory for source files; requires --test-base
50 | -t, --test-base= Base directory for test files; requires --src-base; writes output to that directory
51 |
52 | Format:
53 | %n Name starting with a lower-case letter.
54 | %N Name starting with an upper-case letter.
55 | %t Type starting with a lower-case letter.
56 | %T Type starting with a upper-case letter.
57 |
58 | Format Examples:
59 | "mock_%t" Customer => mock_customer
60 | "%NTest" arg => ArgTest
61 | "testClass" SomeName => TestClass
62 | ```
63 |
64 | ### PHPStorm
65 |
66 | I recommend integrating `test-generator` as an external tool in PHPStorm. This works, because PHPStorm can pass the
67 | filename of the currently active file as an argument to `test-generator`, which will then generate and write the
68 | test to your preconfigured location.
69 |
70 | Navigate to Settings > Tools > External Tools and klick on **+**. Add the following information:
71 |
72 | | Field | Value |
73 | |-------------------|------------------------------------------------------------------------------|
74 | | Name | test-generator |
75 | | Description | Generate Test Stubs |
76 | | Program | `$PhpExecutable` |
77 | | Arguments | `vendor/bin/test-generator $FilePath$ -s base=src -t tests/unit` |
78 | | Working directory | `$ProjectFileDir$` |
79 |
80 | Remember to adjust *Program* and *Arguments* in case you are using the `.phar` file.
81 |
82 | In case you want to generate different tests with different settings and locations, simply create more external tool entries.
83 |
84 | 
85 |
86 | **Pro Tip**: Assign a shortcut to this tool, because you might end up using it a lot ;)
87 |
88 | ## Installation
89 |
90 | ### Composer (PHP 7.1+)
91 |
92 | ```bash
93 | # local install
94 | composer require "mihaeu/test-generator:^1.0"
95 |
96 | # global install
97 | composer global require "mihaeu/test-generator:^1.0"
98 | ```
99 |
100 | ### Phar (PHP 5.5+)
101 |
102 | Since I actually need to use this on 5.5 legacy projects (should work with 5.4 as well, but didn't test for it), I also release a phar file which works for older versions:
103 |
104 | ```bash
105 | wget https://github.com/mihaeu/php-test-generator/releases/download/1.2.0/test-generator-1.2.0.phar
106 | chmod +x test-generator-1.2.0.phar
107 | ```
108 |
109 | **Please note that by doing this we should be disgusted at ourselves for not upgrading to PHP 7.1 (soon 7.2).**
110 |
111 | ### Git
112 |
113 | ```bash
114 | git clone https://github.com/mihaeu/php-test-generator
115 | cd php-test-generator
116 | composer install
117 | bin/test-generator --help
118 | ```
119 |
120 | If you don't have PHP 7.1 installed you can run `bin/remove-php7-features` to convert the source files. I won't however except pull requests without PHP 7.1 support.
121 |
122 | ## Example
123 |
124 | Given a PHP file like:
125 |
126 | ```php
127 | mockNodeTraverser = Mockery::mock(PhpParser\NodeTraverser::class);
180 | $this->mockDependencyInspectionVisitor = Mockery::mock(Mihaeu\PhpDependencies\Analyser\DependencyInspectionVisitor::class);
181 | $this->mockParser = Mockery::mock(Mihaeu\PhpDependencies\Analyser\Parser::class);
182 | $this->classUnderTest = new StaticAnalyser(
183 | $this->mockNodeTraverser,
184 | $this->mockDependencyInspectionVisitor,
185 | $this->mockParser
186 | );
187 | }
188 |
189 | public function testMissing()
190 | {
191 | $this->fail('Test not yet implemented');
192 | }
193 | }
194 | ```
195 |
196 | ## Roadmap
197 |
198 | - avoid FQNs by default by including (`use`) all required namespaces
199 | - `--template=` for custom templates
200 | - and many more features are planned, just [check out the functional backlog](https://github.com/mihaeu/php-test-generator/tree/master/tests/functional/backlog)
201 |
202 | ## Contributing
203 |
204 | If you have any ideas for new features or are willing to contribute yourself you are more than welcome to do so.
205 |
206 | Make sure to keep the code coverage at 100% (and run humbug for mutation testing) and stick to PSR-2. The `Makefile` in the repo is making lots of assumptions and probably won't work on your machine, but it might help.
207 |
208 | ## LICENSE
209 |
210 | > Copyright (c) 2017-2019 Michael Haeuslmann
211 | >
212 | > Permission is hereby granted, free of charge, to any person obtaining a copy
213 | > of this software and associated documentation files (the "Software"), to deal
214 | > in the Software without restriction, including without limitation the rights
215 | > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
216 | > copies of the Software, and to permit persons to whom the Software is
217 | > furnished to do so, subject to the following conditions:
218 | >
219 | > The above copyright notice and this permission notice shall be included in all
220 | > copies or substantial portions of the Software.
221 | >
222 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
223 | > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
224 | > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
225 | > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
226 | > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
227 | > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
228 | > SOFTWARE.
229 |
--------------------------------------------------------------------------------
/bin/test-generator:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 |
21 |
22 | Options:
23 | --php5 Generate PHP5 compatible code [default:false].
24 | --phpunit5 Generate a test for PHPUnit 5 [default:false].
25 | --mockery Generates mocks using Mockery [default:false].
26 | --covers Adds the @covers annotation [default:false].
27 | --base-class= Inherit from this base class e.g. "Example\TestCase".
28 | --subject-format= Format the field for the subject class.
29 | --field-format= Format the fields for dependencies.
30 | -s, --src-base= Base directory for source files; requires --test-base
31 | -t, --test-base= Base directory for test files; requires --src-base; writes output to that directory
32 |
33 | Format:
34 | %n Name starting with a lower-case letter.
35 | %N Name starting with an upper-case letter.
36 | %t Type starting with a lower-case letter.
37 | %T Type starting with a upper-case letter.
38 |
39 | Format Examples:
40 | "mock_%t" Customer => mock_customer
41 | "%NTest" arg => ArgTest
42 | "testClass" SomeName => TestClass
43 | EOT;
44 | $args = Docopt::handle($description, $argv);
45 |
46 | $container = new DependencyContainer($args);
47 | try {
48 | $container->testGenerator()->run(new PhpFile(new SplFileInfo($args[''])));
49 | } catch (Exception $exception) {
50 | echo $description
51 | . PHP_EOL
52 | . PHP_EOL
53 | . '! ' . $exception->getMessage()
54 | . PHP_EOL;
55 | exit(1);
56 | }
57 |
--------------------------------------------------------------------------------
/box.json.dist:
--------------------------------------------------------------------------------
1 | {
2 | "directories": ["src/", "vendor/", "templates/"],
3 | "main": "bin/test-generator",
4 | "output": "build/test-generator.phar",
5 | "stub": true
6 | }
7 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mihaeu/test-generator",
3 | "description": "Generate test cases for existing files",
4 | "type": "library",
5 | "homepage": "https://github.com/mihaeu/php-test-generator",
6 | "keywords": [
7 | "testing",
8 | "generation",
9 | "phpunit",
10 | "mockery"
11 | ],
12 | "bin": ["bin/test-generator"],
13 | "require": {
14 | "php": "^7.1",
15 | "nikic/php-parser": "^4.0",
16 | "docopt/docopt": "^1.0",
17 | "twig/twig": "^1.0"
18 | },
19 | "require-dev": {
20 | "phpunit/phpunit": "^6.5",
21 | "infection/infection": "0.9.*",
22 | "squizlabs/php_codesniffer": "^3.2.3",
23 | "phpmd/phpmd": "^2.6",
24 | "phpstan/phpstan": "^0.11.0"
25 | },
26 | "minimum-stability": "dev",
27 | "prefer-stable": true,
28 | "license": "MIT",
29 | "authors": [
30 | {
31 | "name": "Michael Haeuslmann",
32 | "email": "haeuslmann@gmail.com"
33 | }
34 | ],
35 | "autoload": {
36 | "psr-4": {
37 | "Mihaeu\\TestGenerator\\": ["src/"]
38 | }
39 | },
40 | "autoload-dev": {
41 | "psr-4": {
42 | "Mihaeu\\TestGenerator\\": [
43 | "tests/unit/",
44 | "tests/functional/",
45 | "tests/integration/"
46 | ]
47 | },
48 | "files": ["vendor/phpunit/phpunit/src/Framework/Assert/Functions.php"]
49 | },
50 | "support": {
51 | "issues": "https://github.com/mihaeu/php-test-generator/issues"
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/doc/phpstorm-integration.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mihaeu/php-test-generator/30b4630c996de683c19bb1d44e27fd7cb6fc74a1/doc/phpstorm-integration.png
--------------------------------------------------------------------------------
/doc/testgenerator-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mihaeu/php-test-generator/30b4630c996de683c19bb1d44e27fd7cb6fc74a1/doc/testgenerator-logo.png
--------------------------------------------------------------------------------
/infection.json.dist:
--------------------------------------------------------------------------------
1 | {
2 | "source": {
3 | "directories": [
4 | "src"
5 | ]
6 | },
7 | "timeout": 10,
8 | "logs": {
9 | "text": "build/infection.log",
10 | "summary": "build/summary.log",
11 | "debug": "build/debug.log",
12 | "perMutator": "build/per-mutator.md",
13 | "badge": {
14 | "branch": "master"
15 | }
16 | },
17 | "testFramework":"phpunit"
18 | }
19 |
--------------------------------------------------------------------------------
/phpstan.neon:
--------------------------------------------------------------------------------
1 | parameters:
2 | ignoreErrors:
3 | - '#does not accept PHPUnit_Framework_MockObject_MockObject#'
4 | - '#Call to an undefined method [a-zA-Z0-9\\_]+::method\(\)#'
5 | - '#Access to an undefined property PHPUnit_Framework_MockObject_MockObject::\$[a-zA-Z0-9_]+#'
6 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
15 | tests/unit
16 |
17 |
18 | tests/functional
19 |
20 |
21 | tests/integration
22 |
23 |
24 |
25 |
26 |
27 | src
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/src/ClassAnalyser.php:
--------------------------------------------------------------------------------
1 | name->name === self::CONSTRUCTOR_METHOD_NAME
30 | ) {
31 | $this->parameters = array_reduce($node->getParams(), function (array $parameters, Param $parameter) {
32 | $parameters[$parameter->var->name] = new Dependency(
33 | $parameter->var->name,
34 | $this->generateType($parameter),
35 | $this->generateDefault($parameter)
36 | );
37 | return $parameters;
38 | }, $this->parameters);
39 | } elseif ($node instanceof Node\Stmt\Class_) {
40 | $this->class = Clazz::fromClassNode($node);
41 | }
42 | }
43 |
44 | public function getParameters() : array
45 | {
46 | return $this->parameters;
47 | }
48 |
49 | public function getClass() : ?Clazz
50 | {
51 | return $this->class;
52 | }
53 |
54 | private function generateDefault(Param $parameter) : ?string
55 | {
56 | if ($parameter->default instanceof Expr\Array_) {
57 | return self::TYPE_DEFAULT_ARRAY;
58 | }
59 |
60 | if ($parameter->default) {
61 | return $this->defaultToString($parameter->default);
62 | }
63 |
64 | if ($parameter->type === null) {
65 | return null;
66 | }
67 |
68 | $name = $parameter->type->toString();
69 | if ($name === 'string') {
70 | return self::TYPE_DEFAULT_STRING;
71 | }
72 |
73 | if ($name === 'float') {
74 | return self::TYPE_DEFAULT_FLOAT;
75 | }
76 |
77 | if ($name === 'int') {
78 | return self::TYPE_DEFAULT_INT;
79 | }
80 |
81 | if ($name === 'bool') {
82 | return self::TYPE_DEFAULT_BOOL;
83 | }
84 |
85 | if ($name === 'array') {
86 | return self::TYPE_DEFAULT_ARRAY;
87 | }
88 |
89 | return null;
90 | }
91 |
92 | private function generateType(Param $parameter) : ?string
93 | {
94 | if ($parameter->type) {
95 | return $parameter->type->toString();
96 | }
97 |
98 | return $this->guessTypeFromDefault($parameter);
99 | }
100 |
101 | private function defaultToString(Expr $default) : string
102 | {
103 | if ($default instanceof Expr\ConstFetch) {
104 | if (preg_match('/(false|true)/i', $default->name->toString())) {
105 | return strtolower($default->name->toString());
106 | }
107 | return $default->name->toString();
108 | }
109 |
110 | if (is_string($default->value)) {
111 | return "'" . $default->value . "'";
112 | }
113 |
114 | return (string) $default->value;
115 | }
116 |
117 | private function guessTypeFromDefault(Param $parameter) : ?string
118 | {
119 | if ($parameter->default instanceof Expr\Array_) {
120 | return 'array';
121 | }
122 |
123 | if ($parameter->default instanceof Node\Scalar\LNumber) {
124 | return 'int';
125 | }
126 |
127 | if ($parameter->default instanceof Node\Scalar\DNumber) {
128 | return 'float';
129 | }
130 |
131 | if ($parameter->default instanceof Node\Scalar\String_) {
132 | return 'string';
133 | }
134 |
135 | if ($parameter->default instanceof Expr\ConstFetch
136 | && preg_match('/(true|false)/i', $parameter->default->name->toString())
137 | ) {
138 | return 'bool';
139 | }
140 |
141 | return null;
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/src/Clazz.php:
--------------------------------------------------------------------------------
1 | class = $class;
23 | $this->namespacedName = $namespacedName;
24 | $this->namespace = $namespace;
25 | }
26 |
27 | public function clazz(): string
28 | {
29 | return $this->class;
30 | }
31 |
32 | public static function fromClassNode(Class_ $classNode) : Clazz
33 | {
34 | $namespaceParts = $classNode->namespacedName->parts;
35 | $namespace = count($namespaceParts)
36 | ? implode('\\', array_slice($namespaceParts, 0, -1))
37 | : '';
38 | return new Clazz(
39 | (string) $classNode->name,
40 | implode('\\', $namespaceParts),
41 | $namespace
42 | );
43 | }
44 |
45 | public static function fromFullyQualifiedNameString(string $fqn) : Clazz
46 | {
47 | self::assertNameIsValidPhpIdentifier($fqn);
48 |
49 | $parts = explode('\\', $fqn);
50 | $namespace = implode('\\', array_slice($parts, 0, -1));
51 | return new Clazz($parts[count($parts) - 1], $fqn, $namespace);
52 | }
53 |
54 | private static function assertNameIsValidPhpIdentifier(string $fqn): void
55 | {
56 | if (!preg_match('/^[a-zA-Z_\x7f-\xff][\\a-zA-Z0-9_\x7f-\xff]*$/', $fqn)) {
57 | throw new InvalidFullyQualifiedNameException($fqn);
58 | }
59 | }
60 |
61 | public function toArray() : array
62 | {
63 | return [
64 | 'class' => $this->class,
65 | 'namespacedName' => $this->namespacedName,
66 | 'namespace' => $this->namespace,
67 | ];
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/Dependency.php:
--------------------------------------------------------------------------------
1 | name = $name;
23 | $this->type = $type;
24 | $this->value = $value;
25 | }
26 |
27 | public function name(): string
28 | {
29 | return $this->name;
30 | }
31 |
32 | public function type(): ?string
33 | {
34 | return $this->type;
35 | }
36 |
37 | public function value(): ?string
38 | {
39 | return $this->value;
40 | }
41 |
42 | public function isScalar() : bool
43 | {
44 | return $this->type === 'bool'
45 | || $this->type === 'int'
46 | || $this->type === 'float'
47 | || $this->type === 'array'
48 | || $this->type === 'string'
49 | || $this->type === null;
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/DependencyContainer.php:
--------------------------------------------------------------------------------
1 | args = $args;
27 | }
28 |
29 | /**
30 | * @return TestGenerator
31 | * @throws InvalidFileException
32 | * @throws \Twig_Error_Loader
33 | * @throws \Twig_Error_Runtime
34 | * @throws \Twig_Error_Syntax
35 | */
36 | public function testGenerator(): TestGenerator
37 | {
38 | return new TestGenerator(
39 | $this->parser(),
40 | new ClassAnalyser(),
41 | $this->nodeTraverser(),
42 | $this->twigRenderer(),
43 | $this->outputProcessor()
44 | );
45 | }
46 |
47 | public function nodeTraverser() : NodeTraverser
48 | {
49 | $nodeTraverser = new NodeTraverser();
50 | $nodeTraverser->addVisitor(new NameResolver());
51 | return $nodeTraverser;
52 | }
53 |
54 | public function twigEnvironment() : Twig_Environment
55 | {
56 | $twig = new Twig_Environment(
57 | new Twig_Loader_Filesystem(__DIR__ . '/../templates'),
58 | ['autoescape' => false]
59 | );
60 | $twig->addFilter($this->lcfirstFilter());
61 | $twig->addFilter($this->isNullFilter());
62 | $twig->addFilter($this->transformClazzFilter($this->args['--subject-format'] ?: '%t'));
63 | $twig->addFilter($this->transformDependencyFilter($this->args['--field-format'] ?: '%n'));
64 | return $twig;
65 | }
66 |
67 | public function templateConfiguration() : TemplateConfiguration
68 | {
69 | return new TemplateConfiguration(
70 | $this->baseClass(),
71 | $this->args['--php5'],
72 | $this->args['--phpunit5'],
73 | $this->args['--mockery'],
74 | $this->args['--covers']
75 | );
76 | }
77 |
78 | public function twigRenderer() : TwigRenderer
79 | {
80 | return new TwigRenderer($this->twigEnvironment(), $this->templateConfiguration());
81 | }
82 |
83 | public function parser() : Parser
84 | {
85 | return (new ParserFactory())->create(ParserFactory::PREFER_PHP7);
86 | }
87 |
88 | public function lcfirstFilter(): Twig_SimpleFilter
89 | {
90 | return new Twig_SimpleFilter('lcfirst', 'lcfirst');
91 | }
92 |
93 | public function isNullFilter(): Twig_SimpleFilter
94 | {
95 | return new Twig_SimpleFilter('isNull', function ($x) {
96 | return $x === null;
97 | });
98 | }
99 |
100 | public function transformClazzFilter($format) : Twig_SimpleFilter
101 | {
102 | return new Twig_SimpleFilter('transformClazz', function ($x) use ($format) {
103 | return str_replace(
104 | ['%t', '%T', '%n', '%N'],
105 | [lcfirst($x), ucfirst($x), lcfirst($x), ucfirst($x)],
106 | $format
107 | );
108 | });
109 | }
110 |
111 | public function transformDependencyFilter($format) : Twig_SimpleFilter
112 | {
113 | return new Twig_SimpleFilter('transformDependency', function (Dependency $x) use ($format) {
114 | return str_replace(
115 | ['%t', '%T', '%n', '%N'],
116 | [lcfirst($x->type() ?: ''), ucfirst($x->type() ?: ''), lcfirst($x->name()), ucfirst($x->name())],
117 | $format
118 | );
119 | });
120 | }
121 |
122 | public function baseClass() : Clazz
123 | {
124 | return $this->args['--base-class']
125 | ? Clazz::fromFullyQualifiedNameString($this->args['--base-class'])
126 | : $this->defaultBaseClass();
127 | }
128 |
129 | /**
130 | * @throws InvalidFileException
131 | */
132 | private function outputProcessor(): OutputProcessor
133 | {
134 | return OutputProcessorFactory::create(
135 | $this->args[''],
136 | $this->args['--src-base'] ?? null,
137 | $this->args['--test-base'] ?? null
138 | );
139 | }
140 |
141 | private function defaultBaseClass()
142 | {
143 | if ($this->args['--php5']) {
144 | return Clazz::fromFullyQualifiedNameString('PHPUnit_Framework_TestCase');
145 | }
146 | return Clazz::fromFullyQualifiedNameString('PHPUnit\Framework\TestCase');
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/src/InvalidFullyQualifiedNameException.php:
--------------------------------------------------------------------------------
1 | getPathname() . '" is not a PHP file.');
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/Output/Exception/InvalidFileException.php:
--------------------------------------------------------------------------------
1 | getRealPath(),
15 | $srcBase->getRealPath()
16 | )
17 | );
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Output/Exception/UnableToWriteTestFileException.php:
--------------------------------------------------------------------------------
1 | assertSubjectIsInSrcBase($subjectFile, $srcBase);
31 |
32 | $this->subjectFile = $subjectFile;
33 | $this->srcBase = $srcBase;
34 | $this->testBase = $testBase;
35 | }
36 |
37 | /**
38 | * @param string $output
39 | * @throws InvalidFileException
40 | * @throws UnableToWriteTestFileException
41 | */
42 | public function write(string $output): void
43 | {
44 | $bytesWritten = @file_put_contents($this->pathToTestFile(), $output);
45 | if ($bytesWritten === false) {
46 | throw new UnableToWriteTestFileException;
47 | }
48 | }
49 |
50 | /**
51 | * @throws InvalidFileException
52 | */
53 | private function pathToTestFile(): string
54 | {
55 | $projectPath = str_replace(
56 | $this->srcBase->getRealPath(),
57 | '',
58 | $this->subjectFile->getPathInfo()->getRealPath()
59 | );
60 | $testDirectory = $this->testBase->getPathname() . DIRECTORY_SEPARATOR . $projectPath;
61 | if ((!mkdir($testDirectory, 0777, true)
62 | && !is_dir($testDirectory))
63 | || !is_writable(dirname($testDirectory))
64 | ) {
65 | throw InvalidFileException::becauseFileIsNotWritable($testDirectory);
66 | }
67 |
68 | return realpath($testDirectory)
69 | . DIRECTORY_SEPARATOR
70 | . $this->subjectFile->getBasename(self::PHP_EXTENSION)
71 | . self::TEST_FILE_EXTENSION
72 | . self::PHP_EXTENSION;
73 | }
74 |
75 | private function assertSubjectIsInSrcBase(\SplFileInfo $subjectFile, \SplFileInfo $srcBase): void
76 | {
77 | if (strpos($subjectFile->getRealPath(), $srcBase->getRealPath()) !== 0) {
78 | throw new SubjectNotInSrcBaseException($subjectFile, $srcBase);
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/Output/OutputProcessor.php:
--------------------------------------------------------------------------------
1 | isFile() || $file->getExtension() !== 'php') {
15 | throw new NotAPhpFileException($file);
16 | }
17 |
18 | $this->file = $file;
19 | }
20 |
21 | public function content() : string
22 | {
23 | return file_get_contents($this->file->getRealPath());
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/TemplateConfiguration.php:
--------------------------------------------------------------------------------
1 | baseClass = $baseClass;
32 | $this->php5 = $php5;
33 | $this->phpunit5 = $phpunit5;
34 | $this->mockery = $mockery;
35 | $this->covers = $covers;
36 | }
37 |
38 | public function toArray() : array
39 | {
40 | return [
41 | 'baseClass' => $this->baseClass->toArray(),
42 | 'php5' => $this->php5,
43 | 'phpunit5' => $this->phpunit5,
44 | 'mockery' => $this->mockery,
45 | 'covers' => $this->covers,
46 | ];
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/TestGenerator.php:
--------------------------------------------------------------------------------
1 | parser = $parser;
34 | $this->classAnalyser = $classAnalyser;
35 | $this->nodeTraverser = $nodeTraverser;
36 | $this->nodeTraverser->addVisitor($this->classAnalyser);
37 | $this->twigRenderer = $twigRenderer;
38 | $this->outputProcessor = $outputProcessor;
39 | }
40 |
41 | public function run(PhpFile $file): void
42 | {
43 | $nodes = $this->parser->parse($file->content());
44 | $this->nodeTraverser->traverse($nodes);
45 |
46 | if ($this->classAnalyser->getClass() === null) {
47 | return;
48 | }
49 |
50 | $this->outputProcessor->write(
51 | $this->twigRenderer->render(
52 | $this->classAnalyser->getClass(),
53 | $this->classAnalyser->getParameters()
54 | )
55 | );
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/TwigRenderer.php:
--------------------------------------------------------------------------------
1 | template = $twig->load(self::DEFAULT_TEMPLATE);
22 | $this->templateConfiguration = $templateConfiguration;
23 | }
24 |
25 | public function render(Clazz $class, array $dependencies) : string
26 | {
27 | return $this->template->render(
28 | ['dependencies' => $dependencies]
29 | + $this->templateConfiguration->toArray()
30 | + $class->toArray()
31 | );
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/templates/phpunit.twig:
--------------------------------------------------------------------------------
1 | {{ dependency|transformDependency }} = {{ dependency.value }};
34 | {% elseif not dependency.type %}
35 | $this->{{ dependency|transformDependency }} = null;
36 | {% elseif not mockery %}
37 | $this->{{ dependency|transformDependency }} = $this->createMock({{ dependency.type }}::class);
38 | {% else %}
39 | $this->{{ dependency|transformDependency }} = Mockery::mock({{ dependency.type }}::class);
40 | {% endif %}
41 | {% endfor %}
42 | {% if dependencies %}
43 | $this->{{ class|transformClazz }} = new {{ class }}(
44 | {% for dependency in dependencies %}
45 | $this->{{ dependency|transformDependency }}{% if not loop.last %},{% endif %}
46 |
47 | {% endfor %}
48 | );
49 | {% else %}
50 | $this->{{ class|transformClazz }} = new {{ class }}();
51 | {% endif %}
52 | }
53 |
54 | public function testMissing()
55 | {
56 | $this->fail('Test not yet implemented');
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/tests/functional/FunctionalBaseTest.php:
--------------------------------------------------------------------------------
1 | currentTestFileFilename)) {
21 | unlink($this->currentTestFileFilename);
22 | }
23 | }
24 |
25 | /**
26 | * @dataProvider provideFixtures
27 | *
28 | * @param string $source
29 | * @param string $expected
30 | * @param string $arguments
31 | */
32 | public function testGenerateSimplePhpUnitTestCase(string $source, string $expected, string $arguments) : void
33 | {
34 | $cmd = self::TEST_GENERATOR_BINARY . ' ' . $arguments . ' ' . $this->generateTestFile($source);
35 | $actual = shell_exec($cmd);
36 | assertEquals($expected, $actual);
37 | }
38 |
39 | public function provideFixtures() : array
40 | {
41 | $testArguments = [];
42 | foreach ($this->findFixtures() as $fixtureDir) {
43 | $fixtureDir = self::FIXTURES_DIR . DIRECTORY_SEPARATOR . $fixtureDir;
44 | $arguments = file_exists($fixtureDir . '/arguments.txt')
45 | ? str_replace(["\n", "\r"], ' ', file_get_contents($fixtureDir . '/arguments.txt'))
46 | : '';
47 | $testArguments[$this->camelCaseToReadable($fixtureDir)] = [
48 | file_get_contents($fixtureDir . '/source.php'),
49 | file_get_contents($fixtureDir . '/expected.php'),
50 | $arguments,
51 | ];
52 | }
53 | return $testArguments;
54 | }
55 |
56 | private function generateTestFile(string $content) : string
57 | {
58 | $this->currentTestFileFilename = '/tmp/testfilefortestgenerator.php';
59 | file_put_contents($this->currentTestFileFilename, $content);
60 | return $this->currentTestFileFilename;
61 | }
62 |
63 | private function camelCaseToReadable(string $camelCaseText) : string
64 | {
65 | return strtolower(
66 | trim(
67 | preg_replace('/([A-Z])/', ' $1', basename($camelCaseText))
68 | )
69 | );
70 | }
71 |
72 | private function findFixtures(): array
73 | {
74 | return array_filter(
75 | scandir(self::FIXTURES_DIR, SCANDIR_SORT_ASCENDING),
76 | function (string $dirname) {
77 | return is_dir(self::FIXTURES_DIR . DIRECTORY_SEPARATOR . $dirname)
78 | && strpos($dirname, '.') !== 0;
79 | }
80 | );
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/tests/functional/backlog/AliasFlag/arguments.txt:
--------------------------------------------------------------------------------
1 | --alias='ClassA,B' --alias='PHPUnit_Framework_MockObject_MockObject,Mock'
2 |
--------------------------------------------------------------------------------
/tests/functional/backlog/AliasFlag/expected.php:
--------------------------------------------------------------------------------
1 | classA = $this->createMock(B::class);
18 | $this->a = new A(
19 | $this->b
20 | );
21 | }
22 |
23 | public function testMissing()
24 | {
25 | $this->fail('Test not yet implemented');
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/tests/functional/backlog/AliasFlag/source.php:
--------------------------------------------------------------------------------
1 | classA = $this->createMock(ClassA::class);
16 | $this->a = new A(
17 | $this->classA
18 | );
19 | }
20 |
21 | public function testMissing()
22 | {
23 | $this->fail('Test not yet implemented');
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/tests/functional/backlog/MultipleClassesPerFile/expected2.php:
--------------------------------------------------------------------------------
1 | classB = $this->createMock(ClassB::class);
16 | $this->b = new B(
17 | $this->classB
18 | );
19 | }
20 |
21 | public function testMissing()
22 | {
23 | $this->fail('Test not yet implemented');
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/tests/functional/backlog/MultipleClassesPerFile/source.php:
--------------------------------------------------------------------------------
1 | classA = Mockery::mock(ClassA::class);
18 | $this->a = new A(
19 | $this->classA
20 | );
21 | }
22 |
23 | public function testMissing()
24 | {
25 | $this->fail('Test not yet implemented');
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/tests/functional/backlog/Php5SupportWithMockery/source.php:
--------------------------------------------------------------------------------
1 | classA = $this->createMock(Other\ClassA::class);
21 | $this->a = new A(
22 | $this->classA
23 | );
24 | }
25 |
26 | public function testMissing()
27 | {
28 | $this->fail('Test not yet implemented');
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/tests/functional/fixtures/AddsCoversAnnotation/source.php:
--------------------------------------------------------------------------------
1 | x = null;
46 | $this->bool = false;
47 | $this->int = 0;
48 | $this->string = '';
49 | $this->otherString = 'abc';
50 | $this->otherInt = 999;
51 | $this->otherFloat = 3.1415;
52 | $this->xs = [];
53 | $this->array = [];
54 | $this->caseInsensitive = true;
55 | $this->fixed = PHP_EOL;
56 | $this->a = new A(
57 | $this->x,
58 | $this->bool,
59 | $this->int,
60 | $this->string,
61 | $this->otherString,
62 | $this->otherInt,
63 | $this->otherFloat,
64 | $this->xs,
65 | $this->array,
66 | $this->caseInsensitive,
67 | $this->fixed
68 | );
69 | }
70 |
71 | public function testMissing()
72 | {
73 | $this->fail('Test not yet implemented');
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/tests/functional/fixtures/ClassWithScalarValues/source.php:
--------------------------------------------------------------------------------
1 | classA = $this->createMock(ClassA::class);
18 | $this->a = new A(
19 | $this->classA
20 | );
21 | }
22 |
23 | public function testMissing()
24 | {
25 | $this->fail('Test not yet implemented');
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/tests/functional/fixtures/DifferentBaseClass/source.php:
--------------------------------------------------------------------------------
1 | mockArg = $this->createMock(ClassA::class);
18 | $this->aUnderTest = new A(
19 | $this->mockArg
20 | );
21 | }
22 |
23 | public function testMissing()
24 | {
25 | $this->fail('Test not yet implemented');
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/tests/functional/fixtures/FormattingForFieldsAndTestSubject/source.php:
--------------------------------------------------------------------------------
1 | classA = $this->createMock(ClassA::class);
18 | $this->a = new A(
19 | $this->classA
20 | );
21 | }
22 |
23 | public function testMissing()
24 | {
25 | $this->fail('Test not yet implemented');
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/tests/functional/fixtures/GeneratesSimplePhpUnitTestCase/source.php:
--------------------------------------------------------------------------------
1 | classA = Mockery::mock(ClassA::class);
18 | $this->a = new A(
19 | $this->classA
20 | );
21 | }
22 |
23 | public function testMissing()
24 | {
25 | $this->fail('Test not yet implemented');
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/tests/functional/fixtures/MockeryMocking/source.php:
--------------------------------------------------------------------------------
1 | classA = $this->createMock(ClassA::class);
16 | $this->a = new A(
17 | $this->classA
18 | );
19 | }
20 |
21 | public function testMissing()
22 | {
23 | $this->fail('Test not yet implemented');
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/tests/functional/fixtures/Php5SupportWithPhpUnit5/source.php:
--------------------------------------------------------------------------------
1 | false,
21 | '--phpunit5' => false,
22 | '--mockery' => false,
23 | '--covers' => false,
24 | '--base-class' => 'PHPUnit\Framework\TestCase',
25 | '--subject-format' => false,
26 | '--field-format' => false,
27 | ]);
28 | $this->twigRenderer = (new DependencyContainer($args))->twigRenderer();
29 | }
30 |
31 | public function testRendersTemplate()
32 | {
33 | $expected = <<<'EOT'
34 | customer = $this->createMock(Customer::class);
52 | $this->name = 'test';
53 | $this->test = new Test(
54 | $this->customer,
55 | $this->name
56 | );
57 | }
58 |
59 | public function testMissing()
60 | {
61 | $this->fail('Test not yet implemented');
62 | }
63 | }
64 |
65 | EOT;
66 | $actual = $this->twigRenderer->render(new Clazz('Test', '', ''), [
67 | new Dependency('customer', 'Customer'),
68 | new Dependency('name', 'string', '\'test\''),
69 | ]);
70 | assertEquals($expected, $actual);
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/tests/resources/php7-to-php55-example/example.php:
--------------------------------------------------------------------------------
1 | object = $object;
21 | $this->dependency = $dependency;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/tests/unit/ClassAnalyserTest.php:
--------------------------------------------------------------------------------
1 | classAnalyser = new ClassAnalyser();
36 | }
37 |
38 | public function testOnlyRegistersOnConstructors() : void
39 | {
40 | $classNode = $this->createMock(Class_::class);
41 | $name = $this->createMock(Name::class);
42 | $name->name = 'Test';
43 | $name->parts = ['Test'];
44 | $classNode->name = 'Test';
45 | $classNode->namespacedName = $name;
46 | $this->classAnalyser->enterNode($classNode);
47 | assertEmpty($this->classAnalyser->getParameters());
48 | }
49 |
50 | public function testFindsParametersInConstructors() : void
51 | {
52 | $param = new Param(
53 | new Variable('example'),
54 | null,
55 | new Identifier('A')
56 | );
57 | $methodNode = new ClassMethod(
58 | new Identifier('__construct'),
59 | ['params' => [$param]]
60 | );
61 |
62 | $this->classAnalyser->enterNode($methodNode);
63 |
64 | assertEquals(
65 | ['example' => new Dependency('example', 'A')],
66 | $this->classAnalyser->getParameters()
67 | );
68 | }
69 |
70 | public function testAnalysesOnlyClassConstructors() : void
71 | {
72 | $functionNode = $this->createMock(Function_::class);
73 | $functionNode->name = '__construct';
74 |
75 | $param = $this->createMock(Param::class);
76 | $param->name = 'Example';
77 | $name = $this->createMock(Name::class);
78 | $name->method('toString')->willReturn('A');
79 | $param->type = $name;
80 | $functionNode->method('getParams')->willReturn([$param]);
81 |
82 | $this->classAnalyser->enterNode($functionNode);
83 | assertEmpty($this->classAnalyser->getParameters());
84 | }
85 |
86 | public function testFindsClass() : void
87 | {
88 | $classNode = $this->createMock(Class_::class);
89 | $classNode->name = 'b';
90 | $name = $this->createMock(Name::class);
91 | $name->parts = ['a', 'b'];
92 | $classNode->namespacedName = $name;
93 |
94 | $this->classAnalyser->enterNode($classNode);
95 | $expected = new Clazz('b', 'a\b', 'a');
96 | assertEquals($expected, $this->classAnalyser->getClass());
97 | }
98 |
99 | /**
100 | * @dataProvider parameterProvider
101 | * @param string $message
102 | * @param $type
103 | * @param string|null $default
104 | * @param string|null $expected
105 | */
106 | public function testGeneratesDefaults(string $message, $type, ?string $default, ?string $expected) : void
107 | {
108 | $this->classAnalyser->enterNode(
109 | $this->createConstructorWithOneParamenter($message, $type, $default)
110 | );
111 | $parameters = $this->classAnalyser->getParameters();
112 | assertEquals($expected, array_pop($parameters)->value(), $message);
113 | }
114 |
115 | public function parameterProvider() : array
116 | {
117 | return [
118 | 'Object without default' => [
119 | 'message' => 'Object without default',
120 | 'type' => 'ArrayObject',
121 | 'default' => null,
122 | 'expected' => '',
123 | ],
124 | 'No arguments' => [
125 | 'message' => 'No arguments',
126 | 'type' => null,
127 | 'default' => null,
128 | 'expected' => null,
129 | ],
130 | 'Bool with no default' => [
131 | 'message' => 'Bool with no default',
132 | 'type' => 'bool',
133 | 'default' => null,
134 | 'expected' => self::TYPE_BOOL_FALSE,
135 | ],
136 | 'No type with true default' => [
137 | 'message' => 'No type with true default',
138 | 'type' => null,
139 | 'default' => self::TYPE_BOOL_TRUE,
140 | 'expected' => self::TYPE_BOOL_TRUE,
141 | ],
142 | 'No type with TRUE default' => [
143 | 'message' => 'No type with TRUE default',
144 | 'type' => null,
145 | 'default' => 'TRUE',
146 | 'expected' => self::TYPE_BOOL_TRUE,
147 | ],
148 | 'No type with false default' => [
149 | 'message' => 'No type with false default',
150 | 'type' => null,
151 | 'default' => self::TYPE_BOOL_FALSE,
152 | 'expected' => self::TYPE_BOOL_FALSE,
153 | ],
154 | 'Int with no default' => [
155 | 'message' => 'Int with no default',
156 | 'type' => 'int',
157 | 'default' => null,
158 | 'expected' => '0',
159 | ],
160 | 'No type with int default' => [
161 | 'message' => 'No type with int default',
162 | 'type' => null,
163 | 'default' => '123',
164 | 'expected' => '123',
165 | ],
166 | 'Float type with no default' => [
167 | 'message' => 'Float type with no default',
168 | 'type' => 'float',
169 | 'default' => null,
170 | 'expected' => '0.0',
171 | ],
172 | 'No type with float default' => [
173 | 'message' => 'No type with float default',
174 | 'type' => null,
175 | 'default' => '3.1415',
176 | 'expected' => '3.1415',
177 | ],
178 | 'String with no default' => [
179 | 'message' => 'String with no default',
180 | 'type' => 'string',
181 | 'default' => null,
182 | 'expected' => "''",
183 | ],
184 | 'No type with string default' => [
185 | 'message' => 'No type with string default',
186 | 'type' => null,
187 | 'default' => '"string"',
188 | 'expected' => "'string'",
189 | ],
190 | 'Array type with no default' => [
191 | 'message' => 'Array type with no default',
192 | 'type' => 'array',
193 | 'default' => null,
194 | 'expected' => '[]',
195 | ],
196 | 'No type with array default' => [
197 | 'message' => 'No type with array default',
198 | 'type' => null,
199 | 'default' => '[]',
200 | 'expected' => '[]',
201 | ],
202 | 'No type with defined global constant' => [
203 | 'message' => 'No type with defined global constant',
204 | 'type' => null,
205 | 'default' => 'SOME_CONST',
206 | 'expected' => 'SOME_CONST',
207 | ],
208 | ];
209 | }
210 |
211 | private function createConstructorWithOneParamenter(string $message, ?string $type, $default): ClassMethod
212 | {
213 | $param = new Param(
214 | new Variable(str_replace(' ', '_', $message)),
215 | $this->defaultToNode($default),
216 | $this->typeFromString($type)
217 | );
218 |
219 | return new ClassMethod('__construct', ['params' => [$param]]);
220 | }
221 |
222 | private function typeFromString(?string $typeDefinition): ?Name
223 | {
224 | if ($typeDefinition === null) {
225 | return null;
226 | }
227 |
228 | if ($typeDefinition === 'bool'
229 | || $typeDefinition === 'float'
230 | || $typeDefinition === 'double'
231 | || $typeDefinition === 'int'
232 | || $typeDefinition === 'string'
233 | || $typeDefinition === 'array'
234 | ) {
235 | return new Name($typeDefinition);
236 | }
237 |
238 | return new Name($typeDefinition);
239 | }
240 |
241 | private function defaultToNode($default): ?Expr
242 | {
243 | if ($default === null) {
244 | return null;
245 | }
246 |
247 | if (preg_match(self::REGEX_FLOAT, $default)) {
248 | return new DNumber((float) $default);
249 | }
250 |
251 | if (preg_match(self::REGEX_INT, $default)) {
252 | return new LNumber((int) $default);
253 | }
254 |
255 | if ($default === self::TYPE_BOOL_TRUE
256 | || $default === self::TYPE_BOOL_FALSE
257 | || $default === 'TRUE'
258 | || $default === 'FALSE'
259 | ) {
260 | // $bool = $this->createMock(ConstFetch::class);
261 | // $bool->value = stripos($default, self::TYPE_BOOL_TRUE) !== false;
262 | // $bool->name = $this->createMock(Name::class);
263 | // $bool->name->method('toString')->willReturn($default);
264 | return new ConstFetch(new Name($default));
265 | }
266 |
267 | if ($default === '[]') {
268 | return new Array_();
269 | }
270 |
271 | if (preg_match('/^[\'"]/', $default)) {
272 | return new String_(trim($default, '\'""'));
273 | }
274 |
275 | return new ConstFetch(new Name($default));
276 | }
277 | }
278 |
--------------------------------------------------------------------------------
/tests/unit/ClazzTest.php:
--------------------------------------------------------------------------------
1 | name = 'Test';
30 | $this->namespacedName = 'Namespace\Test';
31 | $this->namespace = 'Namespace';
32 | $this->clazz = new Clazz(
33 | $this->name,
34 | $this->namespacedName,
35 | $this->namespace
36 | );
37 | }
38 |
39 | public function testConvertsToArray() : void
40 | {
41 | assertEquals([
42 | 'class' => 'Test',
43 | 'namespacedName' => 'Namespace\Test',
44 | 'namespace' => 'Namespace',
45 | ], $this->clazz->toArray());
46 | }
47 |
48 | public function testGenerateFromClassNode() : void
49 | {
50 | $classNode = $this->createMock(Class_::class);
51 | $classNode->name = 'Test';
52 | $name = $this->createMock(Name::class);
53 | $name->parts = ['Namespace', 'Test'];
54 | $classNode->namespacedName = $name;
55 | assertEquals([
56 | 'class' => 'Test',
57 | 'namespacedName' => 'Namespace\Test',
58 | 'namespace' => 'Namespace',
59 | ], Clazz::fromClassNode($classNode)->toArray());
60 | }
61 |
62 | public function testGeneratesFromStringWithoutNamespace() : void
63 | {
64 | assertEquals(
65 | new Clazz('Test', 'Test', ''),
66 | Clazz::fromFullyQualifiedNameString('Test')
67 | );
68 | }
69 |
70 |
71 | public function testGeneratesFromStringWithNamespace() : void
72 | {
73 | assertEquals(
74 | new Clazz('Test', 'Vendor\Example\Test', 'Vendor\Example'),
75 | Clazz::fromFullyQualifiedNameString('Vendor\Example\Test')
76 | );
77 | }
78 |
79 | public function testRejectsInvalidPhpIdentifier() : void
80 | {
81 | $this->expectException(InvalidFullyQualifiedNameException::class);
82 | Clazz::fromFullyQualifiedNameString('.');
83 | }
84 |
85 | public function testHasClazz() : void
86 | {
87 | assertEquals('Test', (new Clazz('Test', 'Test', ''))->clazz());
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/tests/unit/DependencyContainerTest.php:
--------------------------------------------------------------------------------
1 | args = $this->createMock(Response::class);
25 | $this->dependencyContainer = new DependencyContainer($this->args);
26 | }
27 |
28 | public function testGeneratesNodeTraverser(): void
29 | {
30 | assertInstanceOf(NodeTraverser::class, $this->dependencyContainer->nodeTraverser());
31 | }
32 |
33 | public function testGeneratesTwig_Environment(): void
34 | {
35 | assertInstanceOf(Twig_Environment::class, $this->dependencyContainer->twigEnvironment());
36 | }
37 |
38 | public function testGeneratesTwigRenderer(): void
39 | {
40 | assertInstanceOf(TwigRenderer::class, $this->dependencyContainer->twigRenderer());
41 | }
42 |
43 | public function testGeneratesParser(): void
44 | {
45 | assertInstanceOf(Parser::class, $this->dependencyContainer->parser());
46 | }
47 |
48 | public function testGeneratesTemplateConfiguration(): void
49 | {
50 | assertInstanceOf(TemplateConfiguration::class, $this->dependencyContainer->templateConfiguration());
51 | }
52 |
53 | public function testGenerateBaseClassFromDefaultForPhpunit6(): void
54 | {
55 | $dependencyContainer = new DependencyContainer(new Response([
56 | '--php5' => false,
57 | '--base-class' => false,
58 | ]));
59 | assertEquals(
60 | new Clazz('TestCase', 'PHPUnit\\Framework\\TestCase', 'PHPUnit\\Framework'),
61 | $dependencyContainer->baseClass()
62 | );
63 | }
64 |
65 | public function testGenerateBaseClassFromDefaultForPhpunit5(): void
66 | {
67 | $dependencyContainer = new DependencyContainer(new Response([
68 | '--php5' => true,
69 | '--base-class' => false,
70 | ]));
71 | assertEquals(
72 | new Clazz('PHPUnit_Framework_TestCase', 'PHPUnit_Framework_TestCase', ''),
73 | $dependencyContainer->baseClass()
74 | );
75 | }
76 |
77 | public function testGenerateTestGenerator(): void
78 | {
79 | $dependencyContainer = new DependencyContainer(new Response([
80 | '--subject-format' => '',
81 | '--field-format' => '',
82 | '--base-class' => '',
83 | '--php5' => '',
84 | '--phpunit5' => '',
85 | '--mockery' => '',
86 | '--covers' => '',
87 | '' => '',
88 | ]));
89 | assertInstanceOf(
90 | TestGenerator::class,
91 | $dependencyContainer->testGenerator()
92 | );
93 | }
94 |
95 | public function testGenerateBaseClass(): void
96 | {
97 | $dependencyContainer = new DependencyContainer(new Response([
98 | '--php5' => true,
99 | '--base-class' => 'Vendor\\Test',
100 | ]));
101 | assertEquals(
102 | new Clazz('Test', 'Vendor\\Test', 'Vendor'),
103 | $dependencyContainer->baseClass()
104 | );
105 | }
106 |
107 | public function testLcfirstFilter(): void
108 | {
109 | $callable = $this->dependencyContainer->lcfirstFilter()->getCallable();
110 | assertEquals('test', $callable('Test'));
111 | }
112 |
113 | public function testIsNullFilter(): void
114 | {
115 | $callable = $this->dependencyContainer->isNullFilter()->getCallable();
116 | assertTrue($callable(null));
117 | assertFalse($callable(false));
118 | assertFalse($callable(''));
119 | assertFalse($callable(0));
120 | }
121 |
122 | /**
123 | * @dataProvider clazzProvider
124 | */
125 | public function testTransformClazzFilter($message, $format, $clazz, $expected): void
126 | {
127 | $callable = $this->dependencyContainer->transformClazzFilter($format)->getCallable();
128 | assertEquals($expected, $callable($clazz), "$message with '$format'");
129 | }
130 |
131 | public function clazzProvider(): array
132 | {
133 | return [
134 | [
135 | 'message' => 'Lowercase name',
136 | 'format' => '%n',
137 | 'clazz' => 'Test',
138 | 'expected' => 'test',
139 | ],
140 | [
141 | 'message' => 'Uppercase name',
142 | 'format' => '%N',
143 | 'clazz' => 'test',
144 | 'expected' => 'Test',
145 | ],
146 | [
147 | 'message' => 'Lowercase type with suffix',
148 | 'format' => '%tMock',
149 | 'clazz' => 'customer',
150 | 'expected' => 'customerMock',
151 | ],
152 | ];
153 | }
154 |
155 | /**
156 | * @dataProvider dependencyProvider
157 | */
158 | public function testTransformDependencyFilter($message, $format, $dependency, $expected): void
159 | {
160 | $callable = $this->dependencyContainer->transformDependencyFilter($format)->getCallable();
161 | assertEquals($expected, $callable($dependency), "$message with '$format'");
162 | }
163 |
164 | public function dependencyProvider(): array
165 | {
166 | return [
167 | [
168 | 'message' => 'Lowercase name',
169 | 'format' => '%n',
170 | 'dependency' => new Dependency('Test'),
171 | 'expected' => 'test',
172 | ],
173 | [
174 | 'message' => 'Uppercase name',
175 | 'format' => '%N',
176 | 'dependency' => new Dependency('test'),
177 | 'expected' => 'Test',
178 | ],
179 | [
180 | 'message' => 'Uppercase type',
181 | 'format' => '%T',
182 | 'dependency' => new Dependency('test', 'stdClass'),
183 | 'expected' => 'StdClass',
184 | ],
185 | [
186 | 'message' => 'Lowercase type with suffix',
187 | 'format' => '%tMock',
188 | 'dependency' => new Dependency('test', 'Customer'),
189 | 'expected' => 'customerMock',
190 | ],
191 | ];
192 | }
193 | }
194 |
--------------------------------------------------------------------------------
/tests/unit/DependencyTest.php:
--------------------------------------------------------------------------------
1 | name());
17 | }
18 |
19 | public function testHasType() : void
20 | {
21 | assertEquals('int', (new Dependency('test', 'int'))->type());
22 | }
23 |
24 | public function testHasValue() : void
25 | {
26 | assertEquals('3.1415', (new Dependency('test', null, '3.1415'))->value());
27 | }
28 |
29 | /**
30 | * @dataProvider typeProvider
31 | */
32 | public function testDetectsIfDependencyTypeIsScalar(string $type, bool $expected) : void
33 | {
34 | assertEquals($expected, (new Dependency('test', $type))->isScalar());
35 | }
36 |
37 | public function typeProvider()
38 | {
39 | return [
40 | ['int', true],
41 | ['float', true],
42 | ['string', true],
43 | ['array', true],
44 | ['bool', true],
45 | ['ArrayObject', false],
46 | ['', false],
47 | ];
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/tests/unit/Output/FileWriterTest.php:
--------------------------------------------------------------------------------
1 | setUp();
34 | }
35 |
36 | public function testWritesOutputToFile(): void
37 | {
38 | $subjectFile = new \SplFileInfo(self::SUBJECT_FILE);
39 | $srcBase = new \SplFileInfo(self::SRC_BASE);
40 | $testBase = new \SplFileInfo(self::TEST_BASE);
41 |
42 | $fileWriter = new FileWriter($subjectFile, $srcBase, $testBase);
43 | $fileWriter->write('Test Output');
44 | assertSame('Test Output', file_get_contents(self::TEST_FILE));
45 | }
46 |
47 | public function testThrowsExceptionIfTestFileIsNotInSrcBase(): void
48 | {
49 | $subjectFile = new \SplFileInfo(self::SUBJECT_FILE);
50 | $srcBase = new \SplFileInfo(sys_get_temp_dir());
51 | $testBase = new \SplFileInfo(self::TEST_BASE);
52 |
53 | $this->expectException(SubjectNotInSrcBaseException::class);
54 | (new FileWriter($subjectFile, $srcBase, $testBase))->write('');
55 | }
56 |
57 | public function testThrowsExceptionIfDirectoryOfUnitTestIsNotWritable(): void
58 | {
59 | $subjectFile = new \SplFileInfo(self::SUBJECT_FILE);
60 | $srcBase = new \SplFileInfo(self::SRC_BASE);
61 | $testBase = new \SplFileInfo('/');
62 |
63 | $this->expectException(InvalidFileException::class);
64 | $this->expectExceptionMessageRegExp('/is not writable/');
65 | (new FileWriter($subjectFile, $srcBase, $testBase))->write('');
66 | }
67 |
68 | public function testThrowsExceptionIfUnitFileCannotBeWritten(): void
69 | {
70 | $subjectFile = new \SplFileInfo(self::SUBJECT_FILE);
71 | $srcBase = new \SplFileInfo(self::SRC_BASE);
72 | $testBase = new \SplFileInfo(self::TEST_BASE);
73 |
74 | @mkdir(self::TEST_BASE);
75 | @touch(self::TEST_FILE);
76 | @chmod(self::TEST_FILE, 0000);
77 | $this->expectException(UnableToWriteTestFileException::class);
78 | (new FileWriter($subjectFile, $srcBase, $testBase))->write('');
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/tests/unit/Output/OutputProcessorFactoryTest.php:
--------------------------------------------------------------------------------
1 | expectException(InvalidFileException::class);
28 | $this->expectExceptionMessageRegExp('/is not readable/');
29 | OutputProcessorFactory::create(
30 | '/sfdsdf',
31 | __DIR__,
32 | ''
33 | );
34 | }
35 |
36 | public function testThrowsExceptionIfSrcBaseDoesNotExist(): void
37 | {
38 | $this->expectException(InvalidFileException::class);
39 | $this->expectExceptionMessageRegExp('/does not exist/');
40 | OutputProcessorFactory::create(
41 | __FILE__,
42 | '/sdfsdfs',
43 | ''
44 | );
45 | }
46 |
47 | public function testThrowsExceptionIfSrcBaseIsNotADirectory(): void
48 | {
49 | $this->expectException(InvalidFileException::class);
50 | $this->expectExceptionMessageRegExp('/does not exist/');
51 | OutputProcessorFactory::create(
52 | __FILE__,
53 | __FILE__,
54 | ''
55 | );
56 | }
57 |
58 | public function testCreatesStdoutWriterByDefault(): void
59 | {
60 | assertInstanceOf(StdoutWriter::class, OutputProcessorFactory::create(
61 | __FILE__,
62 | null,
63 | null
64 | ));
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/tests/unit/Output/StdoutWriterTest.php:
--------------------------------------------------------------------------------
1 | write('Test');
18 | assertSame('Test', ob_get_clean());
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/tests/unit/PhpFileTest.php:
--------------------------------------------------------------------------------
1 | content());
20 | unlink($emptyFilename);
21 | }
22 |
23 | public function testReturnsFileContents() : void
24 | {
25 | $regularFilename = '/tmp/test-generator-regular-file.php';
26 | file_put_contents($regularFilename, 'testdata');
27 | assertEquals('testdata', (new PhpFile(new \SplFileInfo($regularFilename)))->content());
28 | unlink($regularFilename);
29 | }
30 |
31 | public function testDoesNotAcceptDirectories() : void
32 | {
33 | $this->expectException(NotAPhpFileException::class);
34 | new PhpFile(new \SplFileInfo(sys_get_temp_dir()));
35 | }
36 |
37 | public function testDoesNotAcceptDirectoriesThatLookLikePhpFiles() : void
38 | {
39 | $this->expectException(NotAPhpFileException::class);
40 | new PhpFile(new \SplFileInfo(sys_get_temp_dir().'.php'));
41 | }
42 |
43 | public function testDoesNotAcceptFilesWithoutPhpExtension() : void
44 | {
45 | $this->expectException(NotAPhpFileException::class);
46 | new PhpFile(new \SplFileInfo('missing-php-extions.phtml'));
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/tests/unit/TemplateConfigurationTest.php:
--------------------------------------------------------------------------------
1 | clazz = new Clazz('Test', 'Test', '');
33 | $this->php5 = false;
34 | $this->phpunit5 = false;
35 | $this->mockery = false;
36 | $this->templateConfiguration = new TemplateConfiguration(
37 | $this->clazz,
38 | $this->php5,
39 | $this->phpunit5,
40 | $this->mockery,
41 | $this->covers
42 | );
43 | }
44 |
45 | public function testConvertsToArray()
46 | {
47 | assertEquals([
48 | 'baseClass' => [
49 | 'class' => 'Test',
50 | 'namespacedName' => 'Test',
51 | 'namespace' => '',
52 | ],
53 | 'php5' => false,
54 | 'phpunit5' => false,
55 | 'mockery' => false,
56 | 'covers' => false,
57 | ], $this->templateConfiguration->toArray());
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/tests/unit/TestGeneratorTest.php:
--------------------------------------------------------------------------------
1 | parser = $this->createMock(Parser::class);
43 | $this->classAnalyser = $this->createMock(ClassAnalyser::class);
44 | $this->nodeTraverser = $this->createMock(NodeTraverser::class);
45 | $this->twigRenderer = $this->createMock(TwigRenderer::class);
46 | $this->outputProcessor = $this->createMock(OutputProcessor::class);
47 | $this->testGenerator = new TestGenerator(
48 | $this->parser,
49 | $this->classAnalyser,
50 | $this->nodeTraverser,
51 | $this->twigRenderer,
52 | $this->outputProcessor
53 | );
54 | }
55 |
56 | public function testPrintsEmptyTemplateIfFileDoesNotHaveAConstructor() : void
57 | {
58 | $this->parser->method('parse')->willReturn([]);
59 | $this->classAnalyser->method('getClass')->willReturn(null);
60 | $file = $this->createMock(PhpFile::class);
61 | $file->method('content')->willReturn('');
62 | $this->twigRenderer->method('render')->willReturn(new Clazz('', '', ''));
63 | assertEquals('', $this->testGenerator->run($file));
64 | }
65 |
66 | public function testReturnsEmptyStringForFileWithoutClass() : void
67 | {
68 | $emptyFile = $this->createMock(PhpFile::class);
69 | $emptyFile->method('content')->willReturn('');
70 |
71 | $this->parser->method('parse')->willReturn([]);
72 | $this->classAnalyser->method('getClass')->willReturn(null);
73 |
74 | assertEmpty($this->testGenerator->run($emptyFile));
75 | }
76 |
77 | public function testRendersClassAndDependencies() : void
78 | {
79 | $file = $this->createMock(PhpFile::class);
80 | $file->method('content')->willReturn('');
81 |
82 | $this->parser->method('parse')->willReturn([]);
83 | $this->classAnalyser->method('getClass')->willReturn(new Clazz('', '', ''));
84 |
85 | $this->twigRenderer->expects($this->once())->method('render');
86 | $this->testGenerator->run($file);
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/tests/unit/TwigRendererTest.php:
--------------------------------------------------------------------------------
1 | template = $this->createMock(Template::class);
26 | $this->twig = $this->createMock(Twig_Environment::class);
27 | $this->twig->expects($this->once())->method('load')->willReturn($this->template);
28 | $this->twigRenderer = new TwigRenderer($this->twig, new TemplateConfiguration(new Clazz('', '', '')));
29 | }
30 |
31 | public function testRendersClassnameAndParameters()
32 | {
33 | $this->template->expects($this->once())->method('render')->willReturn('test');
34 | assertEquals(
35 | 'test',
36 | $this->twigRenderer->render(new Clazz('', '', ''), [123])
37 | );
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/tools/ensure-docopt-readme-and-app-are-identical:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 | [] [--write]
29 | EOT;
30 |
31 | class Php7FeatureRemover extends NodeVisitorAbstract
32 | {
33 | private static $INVALID_PARAM_TYPES = [
34 | 'bool',
35 | 'int',
36 | 'string',
37 | 'float',
38 | 'boolean',
39 | 'double',
40 | 'iterable',
41 | 'Throwable',
42 | ];
43 |
44 | public function leaveNode(Node $node) {
45 | if ($node instanceof ClassMethod || $node instanceof Function_) {
46 | foreach ($node->params as &$param) {
47 | if (in_array($param->type->name ?? '', self::$INVALID_PARAM_TYPES, true)) {
48 | $param->type = null;
49 | }
50 | if ($param->variadic) {
51 | $param->variadic = false;
52 | }
53 | }
54 | $node->returnType = null;
55 | } else if ($node instanceof Declare_
56 | && $node->declares[0]->key->name ?? '' === 'strict_types') {
57 | return NodeTraverser::REMOVE_NODE;
58 | } else if ($node instanceof ClassConst) {
59 | $node->flags = 0;
60 | } else if ($node instanceof CoalesceNode) {
61 | return $this->convertCoalesceToTernaryWithIsset($node);
62 | } else if ($node instanceof FuncCall) {
63 | return $this->replaceSplatOperator($node);
64 | } else if ($node instanceof StaticCall) {
65 | return $this->replaceSplatOperator($node);
66 | } else if ($node instanceof MethodCall) {
67 | return $this->replaceSplatOperator($node);
68 | }
69 | return null;
70 | }
71 |
72 | /**
73 | * @TODO
74 | */
75 | private function replaceSplatOperator(Node $node): Node
76 | {
77 | return $node;
78 | $callable = [];
79 | $arguments = [];
80 | return new FuncCall(
81 | new Name('call_user_func_array'),
82 | [$callable, $arguments]
83 | );
84 |
85 | }
86 |
87 | private function convertCoalesceToTernaryWithIsset(CoalesceNode $coalesceNode): TernaryNode
88 | {
89 | return new TernaryNode(
90 | new IssetNode([$coalesceNode->left]),
91 | $coalesceNode->left,
92 | $coalesceNode->right
93 | );
94 | }
95 | }
96 |
97 | function main(\Docopt\Response $args)
98 | {
99 | $inputFile = $args[''];
100 | $outputFile = $args[''];
101 |
102 | if (!is_file($inputFile)
103 | || !is_readable($inputFile)
104 | ) {
105 | echo 'Input file is not a file or not readable' . PHP_EOL;
106 | exit(1);
107 | }
108 |
109 | if ($args['--write']) {
110 | $outputFile = $inputFile;
111 | }
112 |
113 | if ($outputFile !== null && !is_writable(dirname($outputFile)))
114 | {
115 | echo 'Output file is not writable' . PHP_EOL;
116 | exit(1);
117 | }
118 |
119 | $parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
120 | $traverser = new NodeTraverser;
121 | $prettyPrinter = new PrettyPrinter\Standard;
122 |
123 | $traverser->addVisitor(new NameResolver);
124 | $traverser->addVisitor(new Php7FeatureRemover);
125 |
126 | try {
127 | $code = file_get_contents($inputFile);
128 | $statements = $traverser->traverse($parser->parse($code));
129 | $code = $prettyPrinter->prettyPrintFile($statements);
130 |
131 | $outputFile
132 | ? file_put_contents($outputFile, $code)
133 | : print($code);
134 | } catch (PhpParser\Error $e) {
135 | echo 'Parse Error: ' . $e->getMessage() . PHP_EOL;
136 | exit(2);
137 | } catch (Exception $exception) {
138 | echo 'Something went wrong.' . PHP_EOL;
139 | exit(3);
140 | }
141 | }
142 |
143 | main(Docopt::handle($description));
144 |
--------------------------------------------------------------------------------