Compare commits

...

130 Commits

Author SHA1 Message Date
bb13bdaa80 docs: updates changelog 2021-06-06 01:07:27 +01:00
10e7cbe006 Merge pull request #310 from bigint/master
Update phpunit/phpunit to 9.5.5
2021-06-05 16:29:02 +01:00
0ad232e9de Update phpunit/phpunit to 9.5.5 2021-06-05 20:47:57 +05:30
91eff755fd Merge pull request #306 from owenvoke/feature/lock-plugins
chore(deps): update minimum plugin versions
2021-06-02 12:43:05 +01:00
c05d287fcc chore(deps): update minimum plugin versions 2021-06-02 09:24:07 +01:00
9133b88d65 release: 1.3.0 2021-05-23 22:06:11 +01:00
93b9afbd27 docs: updates changelog 2021-05-23 22:05:46 +01:00
6c6bba2a04 tests: updates snapshots 2021-05-23 22:02:06 +01:00
dd24b7e347 Merge pull request #300 from pestphp/feat-function_exists
Wrap functions in function_exists
2021-05-23 21:47:25 +01:00
ce896b9c83 Merge pull request #302 from def-studio/show-only-name-for-named-datasets
hides dataset values if they have a name
2021-05-23 21:46:59 +01:00
a3c1a61b59 updates tests
Took 1 minute
2021-05-23 22:17:35 +02:00
98c62f4b1d show only name for named datasets
Took 4 minutes
2021-05-23 22:16:29 +02:00
dd78cc9a50 Wrap functions in function_exists 2021-05-19 09:47:29 +02:00
9027411004 feat(mock-plugin): moves mock stuff to their own plugin 2021-05-15 23:43:37 +01:00
9394aa4649 Merge pull request #289 from pestphp/feat/mock
feat: adds built-in mocking
2021-05-15 01:03:07 +01:00
88dc74bbe4 feat(mock): updates tests 2021-05-15 01:02:58 +01:00
7023cec432 feat(mock): updates tests 2021-05-15 01:01:46 +01:00
99d6fb9f5f feat(mock): updates tests 2021-05-14 23:59:50 +01:00
c9f723530d feat(mock): adds work in progress 2021-05-14 23:52:24 +01:00
3205b571b0 Merge branch 'master' of https://github.com/pestphp/pest 2021-05-14 10:51:54 +01:00
2c4aef5272 release: 1.2.1 2021-05-14 10:51:50 +01:00
4a45a7cc6b Merge pull request #297 from owenvoke/bugfix/laravel-commands
fix: resolve test directory before function call
2021-05-14 10:17:51 +01:00
ea8ab88056 style: apply fixes from PHPStan 2021-05-14 10:05:16 +01:00
564a21badd style: apply fixes from php-cs-fixer 2021-05-14 10:00:53 +01:00
41ce87450f fix: resolve test directory before function call 2021-05-14 09:33:52 +01:00
fb0eef4200 release: 1.2.0 2021-05-13 00:37:55 +01:00
819da37b89 Merge pull request #292 from shuvroroy/patch-1
Remove unused import
2021-05-12 21:21:59 +01:00
8eb9c408a9 Merge pull request #296 from pestphp/feature/php-cs-fixer-3
chore: migrate to PHP-CS-Fixer 3.x
2021-05-12 15:14:06 +01:00
1f10b46402 chore: migrate to PHP-CS-Fixer 3.x 2021-05-12 11:26:09 +01:00
7bb12b73e8 Merge pull request #288 from gregorip02/master
Ignore the absence of the tests folder
2021-05-11 08:19:14 +01:00
b8103697c9 Remove unused import 2021-05-07 12:45:55 +06:00
ca3f8b5702 Merge pull request #291 from olivernybroe/feat-junit
Add Junit support
2021-05-05 17:13:58 +01:00
daa01ea44b Merge pull request #283 from faustbrian/test-directory-config
make test directory configurable
2021-05-05 11:07:10 +01:00
26d577f9c5 separate directory and path by / 2021-05-05 05:07:17 +03:00
43fb711251 Merge branch 'master' into master 2021-05-03 11:47:36 -04:00
567af55a19 make test directory configurable 2021-05-03 18:28:20 +03:00
c6a2e3b4d0 chore: release 1.1.0 2021-05-02 23:41:59 +01:00
a10428efe6 Merge pull request #282 from jordanbrauer/reusable-hooks
Reusable hooks
2021-05-02 19:51:59 +01:00
1440637e41 Add Junit support 2021-05-02 11:22:19 +02:00
14cee66dfd Update README.md 2021-04-20 19:32:33 +01:00
d048d60d04 Ignore the absence of the tests folder 2021-04-10 11:51:13 -04:00
1b9162151c docs: add some method documentation and fix typo 2021-04-07 11:02:00 -05:00
885c9f1f06 test: update the snapshot with new test output 2021-04-07 10:56:19 -05:00
90efcc8a8a feat: add shared/global beforeAll and afterAll hooks 2021-04-07 10:55:19 -05:00
7d35ee9998 feat: add new helper to create static closure chains 2021-04-07 10:53:46 -05:00
584a7ac8a5 test: add cases for global before/after all hooks 2021-04-07 10:52:49 -05:00
f21e45ae64 fix: type declaration for and add small comment RE: merge behaviour 2021-04-07 09:49:53 -05:00
c7d26a27b6 fix: ensure that Pest is loaded before the test file itself 2021-04-07 09:49:12 -05:00
54f9397f47 style: address stan + lint issues 2021-04-06 19:32:49 -05:00
ff44589572 refactor: pack hooks into an array instead of 1 argument per hook 2021-04-06 19:27:50 -05:00
53333b56ab test: adding tests for beforeEach and afterEach + empty tests for *All 2021-04-06 19:26:42 -05:00
6616b6299b docs: update changelog 2021-03-31 16:00:01 +01:00
0bab649156 Merge pull request #284 from owenvoke/feature/update-phpunit
chore(deps): add support for PHPUnit 9.5.4
2021-03-31 15:42:50 +01:00
2de7cb4726 chore(deps): add support for PHPUnit 9.5.4 2021-03-31 15:29:58 +01:00
99500d0cae feat: add shared/global before each hook 2021-03-28 02:03:31 -05:00
7eb5478c42 test: add tests for shared/global before each hooks 2021-03-28 01:54:43 -05:00
d2babb1331 Merge pull request #280 from pristavu/master
Add Dusk command argument --browse
2021-03-25 12:57:46 +00:00
a6cced6b63 Update PestDuskCommand.php
Add Dusk command argument --browse
2021-03-22 01:16:09 +02:00
7917313422 docs: update changelog 2021-03-17 13:44:14 +00:00
e02c22e136 Merge pull request #278 from owenvoke/feature/phpunit-9.5.3
chore(deps): add support for PHPUnit 9.5.3
2021-03-17 13:41:58 +00:00
2157644a39 chore(deps): add support for PHPUnit 9.5.3 2021-03-17 13:38:53 +00:00
b6c2812a91 chore: bump static tests to PHP8.0 2021-03-13 11:14:27 +00:00
4b65d2c426 release: v1.0.3 2021-03-13 11:07:57 +00:00
3589f3d5e7 chore: fixes test suite 2021-03-13 11:06:34 +00:00
1f39b8d239 Merge pull request #269 from jordanbrauer/multiple-suffix-extensions
fix: allow multiple file extensions in test suffix (prevent class & file name syntax errors)
2021-03-13 10:36:02 +00:00
7c3c390cbf chore: bumps dev dependencies 2021-03-12 21:50:23 +00:00
19a1569fa8 cleanup for self-review 2021-02-13 13:31:25 -06:00
9a0240bc7b patch addslashes for windows paths 2021-02-13 12:48:43 -06:00
dd94a843b5 run linter & auto format 2021-02-13 12:14:22 -06:00
9426d08aa2 update snapshot for running tests with failed classnames 2021-02-13 12:12:32 -06:00
fe2fac37f8 address PHPStan warning about use of empty 2021-02-13 11:53:30 -06:00
cabd64df00 rename tests for windows, despite losing some coverage 2021-02-13 11:51:35 -06:00
bb57a54089 simplify quote escape sequence handling 2021-02-13 11:40:08 -06:00
5e0bfba7bf run linter 2021-02-13 11:21:14 -06:00
f6c19e469f in the event of no class name, make one on the fly as an escape hatch 2021-02-13 11:20:16 -06:00
13f09cc662 add helper method to generate a simple random string 2021-02-13 11:19:40 -06:00
a7e2856887 add test for totally invalid PHP class name 2021-02-13 11:19:00 -06:00
721e047485 add another test case with non-standard PHP test file name 2021-02-13 11:18:28 -06:00
301ff155a4 prevent parse errors by escaping the quote used for filename property 2021-02-13 11:17:52 -06:00
40f2065575 catch parse errors and let the user know in a friendlier manner 2021-02-13 11:12:09 -06:00
be906eb823 remove additional str_replace call in favour of adjusting the relative path regexp 2021-02-13 11:08:08 -06:00
2cee825f61 rename test sub directory 2021-02-13 11:07:22 -06:00
ea6308bfdf add tests for vaious file naming conventions resulting in various suffixes 2021-02-13 09:45:41 -06:00
c6a6f7e2ab fix typo 2021-02-13 00:15:54 -06:00
20077c285a WIP proof of concept fix for multiple file extensions in test suffix 2021-02-13 00:14:20 -06:00
fa13016785 docs: update changelog 2021-02-04 09:15:54 +00:00
b81bb9d621 chore(deps): add support for PHPUnit 9.5.2 2021-02-04 09:12:29 +00:00
2deb53c14f docs: fix typo 2021-01-18 09:50:21 +00:00
6b8feed08a docs: update changelog 2021-01-18 09:35:53 +00:00
2fe8e07cf3 Merge pull request #261 from owenvoke/feature/update-phpunit
chore(deps): add support for PHPUnit 9.5.1
2021-01-18 09:31:57 +00:00
d2c907868e chore(deps): add support for PHPUnit 9.5.1 2021-01-18 09:11:38 +00:00
a2900a5e09 Merge pull request #251 from MatanYadaev/fix-expect-phpdoc
Fix `TestCase@expect` phpDoc
2021-01-05 20:19:03 +01:00
df934bacd9 fix TestCase expect phpDoc 2021-01-05 20:42:14 +02:00
b0f03c278d docs: updates changelog 2021-01-03 17:27:11 +01:00
b59b321249 Merge pull request #245 from gpibarra/make-dusk-test
MakeDuskTest
2021-01-02 23:35:32 +01:00
82d6991cf8 docs: updates sponsors 2020-12-28 20:17:23 +01:00
d693d99379 Update src/Laravel/Commands/PestTestCommand.php
Co-authored-by: Owen Voke <development@voke.dev>
2020-12-28 13:51:03 -03:00
50c1136be8 MakeDuskTest 2020-12-28 13:13:30 -03:00
dd491e516c Update FUNDING.yml 2020-12-28 15:54:30 +01:00
078aab0d3d Update FUNDING.yml 2020-12-28 02:00:30 +01:00
89c9f4b428 Update FUNDING.yml 2020-12-28 01:59:56 +01:00
2148e896e2 Update FUNDING.yml 2020-12-28 01:59:30 +01:00
7ba49b2e3e chore: fixes style 2020-12-27 21:42:08 +01:00
54a285f7e3 Update PestInstallCommand.php 2020-12-27 17:40:26 +01:00
3ed20d059c Merge pull request #240 from pestphp/feat/improve-init
feat: improve init files
2020-12-27 16:25:48 +01:00
92b6800f28 feat(improve-init): fixes styleci 2020-12-27 15:39:17 +01:00
29cfd1a2dc feat(improve-init): typos 2020-12-27 15:33:52 +01:00
45c09ea0ed feat(improve-init): wording 2020-12-27 15:28:32 +01:00
424e24d530 feat: improve init files 2020-12-27 15:24:21 +01:00
26b2e3561a docs: update changelog 2020-12-27 11:36:24 +00:00
60aea6798d Merge pull request #239 from gpibarra/patch-1
change binary path pest:dusk
2020-12-27 02:46:08 +01:00
fac3fe3f55 docs: updates changelog 2020-12-26 21:31:38 +01:00
17fac0a488 Update composer.json 2020-12-26 21:19:28 +01:00
6abc2207b2 change binary path pest:dusk
In windows not work
full path is used in [7e05b3ca4f/src/Console/DuskCommand.php (L91)) and [163d5c2bd8/src/Adapters/Laravel/Commands/TestCommand.php (L108))
2020-12-23 15:08:49 -03:00
885d224c5d docs: updates changelog 2020-12-20 17:22:40 +01:00
12441deab8 chore: upgrades version 2020-12-20 17:22:33 +01:00
8852fd14ce Merge pull request #236 from avrahamappel/master
Add test for inheritance with depends
2020-12-20 17:18:53 +01:00
e3814e6d9c chore: fixes static analsysis on uses_classs 2020-12-20 17:12:08 +01:00
e19eba0942 Rebuild snapshots 2020-12-17 23:24:29 -05:00
b6e2763731 Move dependency mapping to TestCase
The `TestCaseFactory::getClassName` was removed since it can have
unexpected results when called too early, as it builds up the test class
prematurely. It is not currently used anywhere else.
2020-12-17 23:24:11 -05:00
f75a3ee865 Add test for inheritance with depends 2020-12-15 22:29:37 -05:00
d707c2f208 Merge pull request #209 from NickSdot/make-root-path-working-in-subdirectory-structures
Get root path based on composer autoloader
2020-12-15 01:58:05 +01:00
a43b86d9eb chore: removes rector 2020-12-13 23:11:50 +01:00
5279d5e913 chore: pin expectations plugin version 2020-12-13 23:09:32 +01:00
df7fb92e03 chore: removes rector 2020-12-13 22:55:09 +01:00
95c3418313 docs: updates changelog 2020-12-13 22:52:06 +01:00
67a8be9347 chore: fixes snapshots 2020-12-13 22:50:13 +01:00
5d7f262f4a feat: removes expectation api for a plugin 2020-12-13 22:35:30 +01:00
ba08f2c11e Changes based on feedback https://github.com/pestphp/pest-intellij/issues/73#issuecomment-709201510 2020-10-15 19:46:16 +08:00
13a8aee049 Get root path from already available and correct path of autoloader 2020-10-14 21:31:28 +08:00
117 changed files with 1366 additions and 2264 deletions

30
.gitattributes vendored
View File

@ -1,16 +1,14 @@
/art export-ignore
/docs export-ignore
/tests export-ignore
/scripts export-ignore
/.github export-ignore
/.php_cs export-ignore
.editorconfig export-ignore
.gitattributes export-ignore
.gitignore export-ignore
.travis.yml export-ignore
phpstan.neon export-ignore
rector.yaml export-ignore
phpunit.xml export-ignore
CHANGELOG.md export-ignore
CONTRIBUTING.md export-ignore
README.md export-ignore
/art export-ignore
/docs export-ignore
/tests export-ignore
/scripts export-ignore
/.github export-ignore
/.php-cs-fixer.dist.php export-ignore
.editorconfig export-ignore
.gitattributes export-ignore
.gitignore export-ignore
phpstan.neon export-ignore
phpunit.xml export-ignore
CHANGELOG.md export-ignore
CONTRIBUTING.md export-ignore
README.md export-ignore

2
.github/FUNDING.yml vendored
View File

@ -1,5 +1,5 @@
# These are supported funding model platforms
github: nunomaduro
github: [nunomaduro,owenvoke,olivernybroe,octoper]
patreon: nunomaduro
custom: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=66BYDWAT92N6L

View File

@ -15,16 +15,13 @@ jobs:
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: 7.4
php-version: 8.0
tools: composer:v2
coverage: none
- name: Install Dependencies
run: composer update --no-interaction --no-progress
- name: Run Rector
run: vendor/bin/rector process src --dry-run
- name: Run PHP-CS-Fixer
run: vendor/bin/php-cs-fixer fix -v --allow-risky=yes --dry-run
@ -43,7 +40,7 @@ jobs:
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: 7.4
php-version: 8.0
tools: composer:v2
coverage: none

3
.gitignore vendored
View File

@ -4,7 +4,8 @@ composer.lock
/vendor/
coverage.xml
.phpunit.result.cache
.php_cs.cache
/.php-cs-fixer.php
.php-cs-fixer.cache
.temp/coverage.php
*.swp
*.swo

View File

@ -6,7 +6,7 @@ $finder = PhpCsFixer\Finder::create()
->in(__DIR__ . DIRECTORY_SEPARATOR . 'scripts')
->in(__DIR__ . DIRECTORY_SEPARATOR . 'stubs')
->in(__DIR__ . DIRECTORY_SEPARATOR . 'src')
->append(['.php_cs']);
->append(['.php-cs-fixer.dist.php']);
$rules = [
'@Symfony' => true,
@ -25,7 +25,7 @@ $rules = [
$rules['increment_style'] = ['style' => 'post'];
return PhpCsFixer\Config::create()
return (new PhpCsFixer\Config())
->setUsingCache(true)
->setRules($rules)
->setFinder($finder);

View File

@ -4,6 +4,78 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).
## [v1.3.1 (2021-06-06)](https://github.com/pestphp/pest/compare/v1.3.0...v1.3.1)
### Added
- Added for PHPUnit 9.5.5 ([#310](https://github.com/pestphp/pest/pull/310))
## [v1.3.0 (2021-05-23)](https://github.com/pestphp/pest/compare/v1.2.1...v1.3.0)
### Added
- Named datasets no longer show the arguments ([#302](https://github.com/pestphp/pest/pull/302))
### Fixed
- Wraps global functions within `function_exists` ([#300](https://github.com/pestphp/pest/pull/300))
## [v1.2.1 (2021-05-14)](https://github.com/pestphp/pest/compare/v1.2.0...v1.2.1)
### Fixed
- Laravel commands failing with new `--test-directory` option ([#297](https://github.com/pestphp/pest/pull/297))
## [v1.2.0 (2021-05-13)](https://github.com/pestphp/pest/compare/v1.1.0...v1.2.0)
### Added
- Adds JUnit / Infection support ([#291](https://github.com/pestphp/pest/pull/291))
- `--test-directory` command line option ([#283](https://github.com/pestphp/pest/pull/283))
## [v1.1.0 (2021-05-02)](https://github.com/pestphp/pest/compare/v1.0.5...v1.1.0)
### Added
- Possibility of "hooks" being added using the "uses" function ([#282](https://github.com/pestphp/pest/pull/282))
## [v1.0.5 (2021-03-31)](https://github.com/pestphp/pest/compare/v1.0.4...v1.0.5)
### Added
- Add `--browse` option to `pest:dusk` command ([#280](https://github.com/pestphp/pest/pull/280))
- Support for PHPUnit 9.5.4 ([#284](https://github.com/pestphp/pest/pull/284))
## [v1.0.4 (2021-03-17)](https://github.com/pestphp/pest/compare/v1.0.3...v1.0.4)
### Added
- Support for PHPUnit 9.5.3 ([#278](https://github.com/pestphp/pest/pull/278))
## [v1.0.3 (2021-03-13)](https://github.com/pestphp/pest/compare/v1.0.2...v1.0.3)
### Added
- Support for test extensions ([#269](https://github.com/pestphp/pest/pull/269))
## [v1.0.2 (2021-02-04)](https://github.com/pestphp/pest/compare/v1.0.1...v1.0.2)
### Added
- Support for PHPUnit 9.5.2 ([#267](https://github.com/pestphp/pest/pull/267))
## [v1.0.1 (2021-01-18)](https://github.com/pestphp/pest/compare/v1.0.0...v1.0.1)
### Added
- Support for PHPUnit 9.5.1 ([#261](https://github.com/pestphp/pest/pull/261))
### Fixed
- Fix `TestCase@expect` PHPDoc tag ([#251](https://github.com/pestphp/pest/pull/251))
## [v1.0.0 (2021-01-03)](https://github.com/pestphp/pest/compare/v0.3.19...v1.0.0)
### Added
- `pest:test --dusk` option ([#245](https://github.com/pestphp/pest/pull/245))
### Changed
- Stable version
- Updates init structure ([#240](https://github.com/pestphp/pest/pull/240))
## [v0.3.19 (2020-12-27)](https://github.com/pestphp/pest/compare/v0.3.18...v0.3.19)
### Fixed
- Fix binary path in `pest:dusk` command ([#239](https://github.com/pestphp/pest/pull/239))
## [v0.3.18 (2020-12-26)](https://github.com/pestphp/pest/compare/v0.3.17...v0.3.18)
### Added
- `toBeJson()` expectation ([plugin-expectations#2](https://github.com/pestphp/pest-plugin-expectations/pull/2))
## [v0.3.17 (2020-12-20)](https://github.com/pestphp/pest/compare/v0.3.16...v0.3.17)
### Fixed
- Class inheritance with `depends()` ([#236](https://github.com/pestphp/pest/pull/236))
## [v0.3.16 (2020-12-13)](https://github.com/pestphp/pest/compare/v0.3.15...v0.3.16)
### Changed
- Moves expectation API for external plugin ([5d7f262](https://github.com/pestphp/pest/commit/5d7f262f4ab280a660a85900f402eebb23abfda8))
## [v0.3.15 (2020-12-04)](https://github.com/pestphp/pest/compare/v0.3.14...v0.3.15)
### Added
- Support for PHPUnit 9.5.0 ([#234](https://github.com/pestphp/pest/pull/234))

View File

@ -21,6 +21,8 @@ We would like to extend our thanks to the following sponsors for funding Pest de
### Premium Sponsors
- **[Scout APM](https://github.com/scoutapp)**
- **[Scout APM](https://scoutapm.com)**
- **[Akaunting](https://akaunting.com)**
- **[Meema](https://meema.io/)**
Pest was created by **[Nuno Maduro](https://twitter.com/enunomaduro)** under the **[Sponsorware license](https://github.com/sponsorware/docs)**. It got open-sourced and is now licensed under the **[MIT license](https://opensource.org/licenses/MIT)**.

View File

@ -19,17 +19,21 @@ use Symfony\Component\Console\Output\OutputInterface;
if (file_exists($vendorPath)) {
include_once $vendorPath;
$autoloadPath = $vendorPath;
} else {
include_once $localPath;
$autoloadPath = $localPath;
}
(new Provider())->register();
$rootPath = getcwd();
// get $rootPath based on $autoloadPath
$rootPath = dirname($autoloadPath, 2);
$argv = new ArgvInput();
$testSuite = TestSuite::getInstance($rootPath);
$testSuite = TestSuite::getInstance($rootPath, $argv->getParameterOption('--test-directory', 'tests'));
$isDecorated = (new ArgvInput())->getParameterOption('--colors', 'always') !== 'never';
$isDecorated = $argv->getParameterOption('--colors', 'always') !== 'never';
$output = new ConsoleOutput(ConsoleOutput::VERBOSITY_NORMAL, $isDecorated);
$container = Container::getInstance();
@ -38,5 +42,14 @@ use Symfony\Component\Console\Output\OutputInterface;
ValidatesEnvironment::in($testSuite);
// lets remove any arguments that PHPUnit does not understand
if ($argv->hasParameterOption('--test-directory')) {
foreach ($_SERVER['argv'] as $key => $value) {
if (strpos($value, '--test-directory') !== false) {
unset($_SERVER['argv'][$key]);
}
}
}
exit($container->get(Command::class)->run($_SERVER['argv']));
})();

View File

@ -19,17 +19,18 @@
"require": {
"php": "^7.3 || ^8.0",
"nunomaduro/collision": "^5.0",
"pestphp/pest-plugin": "^0.3",
"pestphp/pest-plugin-coverage": "^0.3",
"pestphp/pest-plugin-init": "^0.3",
"phpunit/phpunit": ">= 9.3.7 <= 9.5.0"
"pestphp/pest-plugin": "^1.0",
"pestphp/pest-plugin-coverage": "^1.0",
"pestphp/pest-plugin-expectations": "^1.3",
"pestphp/pest-plugin-init": "^1.1",
"phpunit/phpunit": ">= 9.3.7 <= 9.5.5"
},
"autoload": {
"psr-4": {
"Pest\\": "src/"
},
"files": [
"src/globals.php",
"src/Functions.php",
"src/Pest.php"
]
},
@ -42,10 +43,9 @@
]
},
"require-dev": {
"illuminate/console": "^7.16.1",
"illuminate/support": "^7.16.1",
"laravel/dusk": "^6.9.0",
"mockery/mockery": "^1.4.1",
"illuminate/console": "^8.32.1",
"illuminate/support": "^8.32.1",
"laravel/dusk": "^6.13.0",
"pestphp/pest-dev-tools": "dev-master"
},
"minimum-stability": "dev",
@ -58,8 +58,8 @@
"bin/pest"
],
"scripts": {
"lint": "rector process src && php-cs-fixer fix -v",
"test:lint": "php-cs-fixer fix -v --dry-run && rector process src --dry-run",
"lint": "php-cs-fixer fix -v",
"test:lint": "php-cs-fixer fix -v --dry-run",
"test:types": "phpstan analyse --ansi --memory-limit=0",
"test:unit": "php bin/pest --colors=always --exclude-group=integration",
"test:integration": "php bin/pest --colors=always --group=integration",
@ -73,7 +73,7 @@
},
"extra": {
"branch-alias": {
"dev-master": "0.4.x-dev"
"dev-master": "1.x-dev"
},
"pest": {
"plugins": [

View File

@ -7,15 +7,13 @@ parameters:
level: max
paths:
- src
excludes_analyse:
- src/globals.php
checkMissingIterableValueType: true
checkGenericClassInNonGenericObjectType: false
reportUnmatchedIgnoredErrors: true
ignoreErrors:
- "#Undefined variable: \\$this#"
- "#type mixed is not subtype of native#"
- "#is not allowed to extend#"
- "#Language construct eval#"
- "# with null as default value#"
@ -24,10 +22,13 @@ parameters:
- "#Method Pest\\\\Support\\\\Reflection::getParameterClassName\\(\\) has a nullable return type declaration.#"
-
message: '#Call to an undefined method PHPUnit\\Framework\\Test::getName\(\)#'
path: src/TeamCity.php
path: src/Logging
-
message: '#invalid typehint type Pest\\Concerns\\TestCase#'
path: src/TeamCity.php
path: src/Logging
-
message: '#is not subtype of native type PHPUnit\\Framework\\Test#'
path: src/TeamCity.php
path: src/Logging
-
message: '#Call to an undefined method PHPUnit\\Framework\\Test::getPrintableTestCaseName\(\)#'
path: src/Logging

View File

@ -1,35 +0,0 @@
<?php
declare(strict_types=1);
use Rector\Core\Configuration\Option;
use Rector\Php70\Rector\StaticCall\StaticCallOnNonStaticToInstanceCallRector;
use Rector\Set\ValueObject\SetList;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
$parameters = $containerConfigurator->parameters();
$parameters->set(Option::AUTO_IMPORT_NAMES, true);
$parameters->set(Option::SETS, [
SetList::ACTION_INJECTION_TO_CONSTRUCTOR_INJECTION,
SetList::ARRAY_STR_FUNCTIONS_TO_STATIC_CALL,
SetList::CODE_QUALITY,
SetList::PHP_53,
SetList::PHP_54,
SetList::PHP_56,
SetList::PHP_70,
SetList::PHP_71,
SetList::PHP_72,
SetList::PHP_73,
SetList::PHPSTAN,
SetList::PHPUNIT_CODE_QUALITY,
SetList::SOLID,
]);
$parameters->set(Option::PATHS, [__DIR__ . '/src', __DIR__ . '/tests']);
$parameters->set(Option::EXCLUDE_RECTORS, [
StaticCallOnNonStaticToInstanceCallRector::class,
]);
};

View File

@ -5,7 +5,8 @@ declare(strict_types=1);
namespace Pest\Actions;
use NunoMaduro\Collision\Adapters\Phpunit\Printer;
use Pest\TeamCity;
use Pest\Logging\JUnit;
use Pest\Logging\TeamCity;
use PHPUnit\TextUI\DefaultResultPrinter;
/**
@ -32,6 +33,14 @@ final class AddsDefaults
$arguments[self::PRINTER] = new TeamCity($arguments['verbose'] ?? false, $arguments['colors'] ?? DefaultResultPrinter::COLOR_ALWAYS);
}
// Load our junit logger instead.
if (array_key_exists('junitLogfile', $arguments)) {
$arguments['listeners'][] = new JUnit(
$arguments['junitLogfile']
);
unset($arguments['junitLogfile']);
}
return $arguments;
}
}

View File

@ -5,7 +5,7 @@ declare(strict_types=1);
namespace Pest\Actions;
use Pest\Support\Str;
use PHPUnit\TextUI\Configuration\Configuration;
use function Pest\testDirectory;
use PHPUnit\Util\FileLoader;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
@ -21,6 +21,7 @@ final class LoadStructure
* @var array<int, string>
*/
private const STRUCTURE = [
'Expectations.php',
'Datasets.php',
'Helpers.php',
'Pest.php',
@ -32,7 +33,7 @@ final class LoadStructure
*/
public static function in(string $rootPath): void
{
$testsPath = $rootPath . DIRECTORY_SEPARATOR . 'tests';
$testsPath = $rootPath . DIRECTORY_SEPARATOR . testDirectory();
$load = function ($filename): bool {
return file_exists($filename) && (bool) FileLoader::checkAndLoad($filename);

View File

@ -1,55 +0,0 @@
<?php
declare(strict_types=1);
namespace Pest\Concerns;
use BadMethodCallException;
use Closure;
/**
* @internal
*/
trait Extendable
{
/**
* @var array<string, Closure>
*/
private static $extends = [];
/**
* Register a custom extend.
*/
public static function extend(string $name, Closure $extend): void
{
static::$extends[$name] = $extend;
}
/**
* Checks if extend is registered.
*/
public static function hasExtend(string $name): bool
{
return array_key_exists($name, static::$extends);
}
/**
* Dynamically handle calls to the class.
*
* @param array<int, mixed> $parameters
*
* @return mixed
*
* @throws BadMethodCallException
*/
public function __call(string $method, array $parameters)
{
if (!static::hasExtend($method)) {
throw new BadMethodCallException(sprintf('Method %s::%s does not exist.', static::class, $method));
}
$extend = static::$extends[$method]->bindTo($this, static::class);
return $extend(...$parameters);
}
}

View File

@ -5,10 +5,10 @@ declare(strict_types=1);
namespace Pest\Concerns;
use Closure;
use Pest\Expectation;
use Pest\Support\ChainableClosure;
use Pest\Support\ExceptionTrace;
use Pest\TestSuite;
use PHPUnit\Util\Test;
use PHPUnit\Framework\ExecutionOrderDependency;
use Throwable;
/**
@ -34,6 +34,38 @@ trait TestCase
*/
private $__test;
/**
* Holds a global/shared beforeEach ("set up") closure if one has been
* defined.
*
* @var Closure|null
*/
private $beforeEach = null;
/**
* Holds a global/shared afterEach ("tear down") closure if one has been
* defined.
*
* @var Closure|null
*/
private $afterEach = null;
/**
* Holds a global/shared beforeAll ("set up before") closure if one has been
* defined.
*
* @var Closure|null
*/
private static $beforeAll = null;
/**
* Holds a global/shared afterAll ("tear down after") closure if one has
* been defined.
*
* @var Closure|null
*/
private static $afterAll = null;
/**
* Creates a new instance of the test case.
*/
@ -55,6 +87,86 @@ trait TestCase
$this->setGroups($groups);
}
/**
* Add dependencies to the test case and map them to instances of ExecutionOrderDependency.
*/
public function addDependencies(array $tests): void
{
$className = get_class($this);
$tests = array_map(function (string $test) use ($className): ExecutionOrderDependency {
if (strpos($test, '::') === false) {
$test = "{$className}::{$test}";
}
return new ExecutionOrderDependency($test, null, '');
}, $tests);
$this->setDependencies($tests);
}
/**
* Add a shared/"global" before all test hook that will execute **before**
* the test defined `beforeAll` hook(s).
*/
public function addBeforeAll(?Closure $hook): void
{
if (!$hook) {
return;
}
self::$beforeAll = (self::$beforeAll instanceof Closure)
? ChainableClosure::fromStatic(self::$beforeAll, $hook)
: $hook;
}
/**
* Add a shared/"global" after all test hook that will execute **before**
* the test defined `afterAll` hook(s).
*/
public function addAfterAll(?Closure $hook): void
{
if (!$hook) {
return;
}
self::$afterAll = (self::$afterAll instanceof Closure)
? ChainableClosure::fromStatic(self::$afterAll, $hook)
: $hook;
}
/**
* Add a shared/"global" before each test hook that will execute **before**
* the test defined `beforeEach` hook.
*/
public function addBeforeEach(?Closure $hook): void
{
$this->addHook('beforeEach', $hook);
}
/**
* Add a shared/"global" after each test hook that will execute **before**
* the test defined `afterEach` hook.
*/
public function addAfterEach(?Closure $hook): void
{
$this->addHook('afterEach', $hook);
}
/**
* Add a shared/global hook and compose them if more than one is passed.
*/
private function addHook(string $property, ?Closure $hook): void
{
if (!$hook) {
return;
}
$this->{$property} = ($this->{$property} instanceof Closure)
? ChainableClosure::from($this->{$property}, $hook)
: $hook;
}
/**
* Returns the test case name. Note that, in Pest
* we ignore withDataset argument as the description
@ -79,6 +191,10 @@ trait TestCase
$beforeAll = TestSuite::getInstance()->beforeAll->get(self::$__filename);
if (self::$beforeAll instanceof Closure) {
$beforeAll = ChainableClosure::fromStatic(self::$beforeAll, $beforeAll);
}
call_user_func(Closure::bind($beforeAll, null, self::class));
}
@ -89,21 +205,15 @@ trait TestCase
{
$afterAll = TestSuite::getInstance()->afterAll->get(self::$__filename);
if (self::$afterAll instanceof Closure) {
$afterAll = ChainableClosure::fromStatic(self::$afterAll, $afterAll);
}
call_user_func(Closure::bind($afterAll, null, self::class));
parent::tearDownAfterClass();
}
/**
* Creates a new expectation.
*
* @param mixed $value
*/
public function expect($value): Expectation
{
return new Expectation($value);
}
/**
* Gets executed before the test.
*/
@ -115,6 +225,10 @@ trait TestCase
$beforeEach = TestSuite::getInstance()->beforeEach->get(self::$__filename);
if ($this->beforeEach instanceof Closure) {
$beforeEach = ChainableClosure::from($this->beforeEach, $beforeEach);
}
$this->__callClosure($beforeEach, func_get_args());
}
@ -125,6 +239,10 @@ trait TestCase
{
$afterEach = TestSuite::getInstance()->afterEach->get(self::$__filename);
if ($this->afterEach instanceof Closure) {
$afterEach = ChainableClosure::from($this->afterEach, $afterEach);
}
$this->__callClosure($afterEach, func_get_args());
parent::tearDown();

View File

@ -90,8 +90,6 @@ final class Command extends BaseCommand
*/
$this->arguments = AddsDefaults::to($this->arguments);
LoadStructure::in($this->testSuite->rootPath);
$testRunner = new TestRunner($this->arguments['loader']);
$testSuite = $this->arguments['test'];
@ -127,6 +125,8 @@ final class Command extends BaseCommand
*/
public function run(array $argv, bool $exit = true): int
{
LoadStructure::in($this->testSuite->rootPath);
$result = parent::run($argv, false);
/*

View File

@ -18,14 +18,14 @@ final class Datasets
/**
* Holds the datasets.
*
* @var array<string, \Closure|iterable<int, mixed>>
* @var array<int|string, Closure|iterable<int|string, mixed>>
*/
private static $datasets = [];
/**
* Sets the given.
*
* @param Closure|iterable<int, mixed> $data
* @param Closure|iterable<int|string, mixed> $data
*/
public static function set(string $name, $data): void
{
@ -37,7 +37,7 @@ final class Datasets
}
/**
* @return Closure|iterable<int, mixed>
* @return Closure|iterable<int|string, mixed>
*/
public static function get(string $name)
{
@ -111,8 +111,10 @@ final class Datasets
{
$exporter = new Exporter();
$nameInsert = is_string($key) ? \sprintf('data set "%s" ', $key) : '';
if (is_int($key)) {
return \sprintf(' with (%s)', $exporter->shortenedRecursiveExport($data));
}
return \sprintf(' with %s(%s)', $nameInsert, $exporter->shortenedRecursiveExport($data));
return \sprintf(' with data set "%s"', $key);
}
}

View File

@ -1,24 +0,0 @@
<?php
declare(strict_types=1);
namespace Pest\Exceptions;
use InvalidArgumentException;
use NunoMaduro\Collision\Contracts\RenderlessEditor;
use NunoMaduro\Collision\Contracts\RenderlessTrace;
use Symfony\Component\Console\Exception\ExceptionInterface;
/**
* @internal
*/
final class InvalidUsesPath extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
{
/**
* Creates a new instance of invalid uses path.
*/
public function __construct(string $target)
{
parent::__construct(sprintf('The path `%s` is not valid.', $target));
}
}

View File

@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace Pest\Exceptions;
use InvalidArgumentException;
use NunoMaduro\Collision\Contracts\RenderlessEditor;
use NunoMaduro\Collision\Contracts\RenderlessTrace;
use Symfony\Component\Console\Exception\ExceptionInterface;
/**
* @internal
*/
final class MissingDependency extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
{
/**
* Creates a new instance of missing dependency.
*/
public function __construct(string $feature, string $dependency)
{
parent::__construct(sprintf('The feature "%s" requires "%s".', $feature, $dependency));
}
}

View File

@ -1,618 +0,0 @@
<?php
declare(strict_types=1);
namespace Pest;
use Pest\Concerns\Extendable;
use PHPUnit\Framework\Assert;
use PHPUnit\Framework\Constraint\Constraint;
use SebastianBergmann\Exporter\Exporter;
/**
* @internal
*
* @property Expectation $not Creates the opposite expectation.
*/
final class Expectation
{
use Extendable;
/**
* The expectation value.
*
* @readonly
*
* @var mixed
*/
public $value;
/**
* The exporter instance, if any.
*
* @readonly
*
* @var Exporter|null
*/
private $exporter;
/**
* Creates a new expectation.
*
* @param mixed $value
*/
public function __construct($value)
{
$this->value = $value;
}
/**
* Creates a new expectation.
*
* @param mixed $value
*/
public function and($value): Expectation
{
return new self($value);
}
/**
* Creates the opposite expectation for the value.
*/
public function not(): OppositeExpectation
{
return new OppositeExpectation($this);
}
/**
* Asserts that two variables have the same type and
* value. Used on objects, it asserts that two
* variables reference the same object.
*
* @param mixed $expected
*/
public function toBe($expected): Expectation
{
Assert::assertSame($expected, $this->value);
return $this;
}
/**
* Asserts that the value is empty.
*/
public function toBeEmpty(): Expectation
{
Assert::assertEmpty($this->value);
return $this;
}
/**
* Asserts that the value is true.
*/
public function toBeTrue(): Expectation
{
Assert::assertTrue($this->value);
return $this;
}
/**
* Asserts that the value is false.
*/
public function toBeFalse(): Expectation
{
Assert::assertFalse($this->value);
return $this;
}
/**
* Asserts that the value is greater than $expected.
*
* @param int|float $expected
*/
public function toBeGreaterThan($expected): Expectation
{
Assert::assertGreaterThan($expected, $this->value);
return $this;
}
/**
* Asserts that the value is greater than or equal to $expected.
*
* @param int|float $expected
*/
public function toBeGreaterThanOrEqual($expected): Expectation
{
Assert::assertGreaterThanOrEqual($expected, $this->value);
return $this;
}
/**
* Asserts that the value is less than or equal to $expected.
*
* @param int|float $expected
*/
public function toBeLessThan($expected): Expectation
{
Assert::assertLessThan($expected, $this->value);
return $this;
}
/**
* Asserts that the value is less than $expected.
*
* @param int|float $expected
*/
public function toBeLessThanOrEqual($expected): Expectation
{
Assert::assertLessThanOrEqual($expected, $this->value);
return $this;
}
/**
* Asserts that $needle is an element of the value.
*
* @param mixed $needle
*/
public function toContain($needle): Expectation
{
if (is_string($this->value)) {
Assert::assertStringContainsString($needle, $this->value);
} else {
Assert::assertContains($needle, $this->value);
}
return $this;
}
/**
* Asserts that the value starts with $expected.
*/
public function toStartWith(string $expected): Expectation
{
Assert::assertStringStartsWith($expected, $this->value);
return $this;
}
/**
* Asserts that the value ends with $expected.
*/
public function toEndWith(string $expected): Expectation
{
Assert::assertStringEndsWith($expected, $this->value);
return $this;
}
/**
* Asserts that $count matches the number of elements of the value.
*/
public function toHaveCount(int $count): Expectation
{
Assert::assertCount($count, $this->value);
return $this;
}
/**
* Asserts that the value contains the property $name.
*
* @param mixed $value
*/
public function toHaveProperty(string $name, $value = null): Expectation
{
$this->toBeObject();
Assert::assertTrue(property_exists($this->value, $name));
if (func_num_args() > 1) {
/* @phpstan-ignore-next-line */
Assert::assertEquals($value, $this->value->{$name});
}
return $this;
}
/**
* Asserts that two variables have the same value.
*
* @param mixed $expected
*/
public function toEqual($expected): Expectation
{
Assert::assertEquals($expected, $this->value);
return $this;
}
/**
* Asserts that two variables have the same value.
* The contents of $expected and the $this->value are
* canonicalized before they are compared. For instance, when the two
* variables $expected and $this->value are arrays, then these arrays
* are sorted before they are compared. When $expected and $this->value
* are objects, each object is converted to an array containing all
* private, protected and public attributes.
*
* @param mixed $expected
*/
public function toEqualCanonicalizing($expected): Expectation
{
Assert::assertEqualsCanonicalizing($expected, $this->value);
return $this;
}
/**
* Asserts that the absolute difference between the value and $expected
* is lower than $delta.
*
* @param mixed $expected
*/
public function toEqualWithDelta($expected, float $delta): Expectation
{
Assert::assertEqualsWithDelta($expected, $this->value, $delta);
return $this;
}
/**
* Asserts that the value is infinite.
*/
public function toBeInfinite(): Expectation
{
Assert::assertInfinite($this->value);
return $this;
}
/**
* Asserts that the value is an instance of $class.
*
* @param string $class
*/
public function toBeInstanceOf($class): Expectation
{
/* @phpstan-ignore-next-line */
Assert::assertInstanceOf($class, $this->value);
return $this;
}
/**
* Asserts that the value is an array.
*/
public function toBeArray(): Expectation
{
Assert::assertIsArray($this->value);
return $this;
}
/**
* Asserts that the value is of type bool.
*/
public function toBeBool(): Expectation
{
Assert::assertIsBool($this->value);
return $this;
}
/**
* Asserts that the value is of type callable.
*/
public function toBeCallable(): Expectation
{
Assert::assertIsCallable($this->value);
return $this;
}
/**
* Asserts that the value is of type float.
*/
public function toBeFloat(): Expectation
{
Assert::assertIsFloat($this->value);
return $this;
}
/**
* Asserts that the value is of type int.
*/
public function toBeInt(): Expectation
{
Assert::assertIsInt($this->value);
return $this;
}
/**
* Asserts that the value is of type iterable.
*/
public function toBeIterable(): Expectation
{
Assert::assertIsIterable($this->value);
return $this;
}
/**
* Asserts that the value is of type numeric.
*/
public function toBeNumeric(): Expectation
{
Assert::assertIsNumeric($this->value);
return $this;
}
/**
* Asserts that the value is of type object.
*/
public function toBeObject(): Expectation
{
Assert::assertIsObject($this->value);
return $this;
}
/**
* Asserts that the value is of type resource.
*/
public function toBeResource(): Expectation
{
Assert::assertIsResource($this->value);
return $this;
}
/**
* Asserts that the value is of type scalar.
*/
public function toBeScalar(): Expectation
{
Assert::assertIsScalar($this->value);
return $this;
}
/**
* Asserts that the value is of type string.
*/
public function toBeString(): Expectation
{
Assert::assertIsString($this->value);
return $this;
}
/**
* Asserts that the value is NAN.
*/
public function toBeNan(): Expectation
{
Assert::assertNan($this->value);
return $this;
}
/**
* Asserts that the value is null.
*/
public function toBeNull(): Expectation
{
Assert::assertNull($this->value);
return $this;
}
/**
* Asserts that the value array has the provided $key.
*
* @param string|int $key
* @param mixed $value
*/
public function toHaveKey($key, $value = null): Expectation
{
if (is_object($this->value) && method_exists($this->value, 'toArray')) {
$array = $this->value->toArray();
} else {
$array = (array) $this->value;
}
Assert::assertArrayHasKey($key, $array);
if (func_num_args() > 1) {
Assert::assertEquals($value, $array[$key]);
}
return $this;
}
/**
* Asserts that the value array has the provided $keys.
*
* @param array<int, int|string> $keys
*/
public function toHaveKeys(array $keys): Expectation
{
foreach ($keys as $key) {
$this->toHaveKey($key);
}
return $this;
}
/**
* Asserts that the value is a directory.
*/
public function toBeDirectory(): Expectation
{
Assert::assertDirectoryExists($this->value);
return $this;
}
/**
* Asserts that the value is a directory and is readable.
*/
public function toBeReadableDirectory(): Expectation
{
Assert::assertDirectoryIsReadable($this->value);
return $this;
}
/**
* Asserts that the value is a directory and is writable.
*/
public function toBeWritableDirectory(): Expectation
{
Assert::assertDirectoryIsWritable($this->value);
return $this;
}
/**
* Asserts that the value is a file.
*/
public function toBeFile(): Expectation
{
Assert::assertFileExists($this->value);
return $this;
}
/**
* Asserts that the value is a file and is readable.
*/
public function toBeReadableFile(): Expectation
{
Assert::assertFileIsReadable($this->value);
return $this;
}
/**
* Asserts that the value is a file and is writable.
*/
public function toBeWritableFile(): Expectation
{
Assert::assertFileIsWritable($this->value);
return $this;
}
/**
* Asserts that the value array matches the given array subset.
*
* @param array<int|string, mixed> $array
*/
public function toMatchArray($array): Expectation
{
if (is_object($this->value) && method_exists($this->value, 'toArray')) {
$valueAsArray = $this->value->toArray();
} else {
$valueAsArray = (array) $this->value;
}
foreach ($array as $key => $value) {
Assert::assertArrayHasKey($key, $valueAsArray);
Assert::assertEquals(
$value,
$valueAsArray[$key],
sprintf(
'Failed asserting that an array has a key %s with the value %s.',
$this->export($key),
$this->export($valueAsArray[$key]),
),
);
}
return $this;
}
/**
* Asserts that the value object matches a subset
* of the properties of an given object.
*
* @param array<string, mixed>|object $object
*/
public function toMatchObject($object): Expectation
{
foreach ((array) $object as $property => $value) {
Assert::assertTrue(property_exists($this->value, $property));
/* @phpstan-ignore-next-line */
$propertyValue = $this->value->{$property};
Assert::assertEquals(
$value,
$propertyValue,
sprintf(
'Failed asserting that an object has a property %s with the value %s.',
$this->export($property),
$this->export($propertyValue),
),
);
}
return $this;
}
/**
* Asserts that the value matches a regular expression.
*/
public function toMatch(string $expression): Expectation
{
Assert::assertMatchesRegularExpression($expression, $this->value);
return $this;
}
/**
* Asserts that the value matches a constraint.
*/
public function toMatchConstraint(Constraint $constraint): Expectation
{
Assert::assertThat($this->value, $constraint);
return $this;
}
/**
* Exports the given value.
*
* @param mixed $value
*/
private function export($value): string
{
if ($this->exporter === null) {
$this->exporter = new Exporter();
}
return $this->exporter->export($value);
}
/**
* Dynamically calls methods on the class without any arguments.
*
* @return Expectation
*/
public function __get(string $name)
{
/* @phpstan-ignore-next-line */
return $this->{$name}();
}
}

View File

@ -5,14 +5,17 @@ declare(strict_types=1);
namespace Pest\Factories;
use Closure;
use ParseError;
use Pest\Concerns;
use Pest\Contracts\HasPrintableTestCaseName;
use Pest\Datasets;
use Pest\Exceptions\ShouldNotHappen;
use Pest\Support\HigherOrderMessageCollection;
use Pest\Support\NullClosure;
use Pest\Support\Str;
use Pest\TestSuite;
use PHPUnit\Framework\TestCase;
use RuntimeException;
/**
* @internal
@ -139,6 +142,7 @@ final class TestCaseFactory
$proxies->proxy($this);
$chains->chain($this);
/* @phpstan-ignore-next-line */
return call_user_func(Closure::bind($factoryTest, $this, get_class($this)), ...func_get_args());
};
@ -156,14 +160,6 @@ final class TestCaseFactory
return array_map($createTest, array_keys($datasets), $datasets);
}
/**
* Makes a fully qualified class name from the current filename.
*/
public function getClassName(): string
{
return $this->makeClassFromFilename($this->filename);
}
/**
* Makes a fully qualified class name from the given filename.
*/
@ -176,7 +172,7 @@ final class TestCaseFactory
}, $filename);
}
$filename = (string) realpath($filename);
$filename = str_replace('\\\\', '\\', addslashes((string) realpath($filename)));
$rootPath = TestSuite::getInstance()->rootPath;
$relativePath = str_replace($rootPath . DIRECTORY_SEPARATOR, '', $filename);
$relativePath = dirname(ucfirst($relativePath)) . DIRECTORY_SEPARATOR . basename($relativePath, '.php');
@ -184,8 +180,12 @@ final class TestCaseFactory
// Strip out any %-encoded octets.
$relativePath = (string) preg_replace('|%[a-fA-F0-9][a-fA-F0-9]|', '', $relativePath);
// Remove escaped quote sequences (maintain namespace)
$relativePath = str_replace(array_map(function (string $quote): string {
return sprintf('\\%s', $quote);
}, ['\'', '"']), '', $relativePath);
// Limit to A-Z, a-z, 0-9, '_', '-'.
$relativePath = (string) preg_replace('/[^A-Za-z0-9.\\\]/', '', $relativePath);
$relativePath = (string) preg_replace('/[^A-Za-z0-9\\\\]/', '', $relativePath);
$classFQN = 'P\\' . $relativePath;
if (class_exists($classFQN)) {
@ -202,15 +202,24 @@ final class TestCaseFactory
$namespace = implode('\\', $partsFQN);
$baseClass = sprintf('\%s', $this->class);
eval("
namespace $namespace;
if ('' === trim($className)) {
$className = 'InvalidTestName' . Str::random();
$classFQN .= $className;
}
final class $className extends $baseClass implements $hasPrintableTestCaseClassFQN {
$traitsCode
try {
eval("
namespace $namespace;
private static \$__filename = '$filename';
}
");
final class $className extends $baseClass implements $hasPrintableTestCaseClassFQN {
$traitsCode
private static \$__filename = '$filename';
}
");
} catch (ParseError $caught) {
throw new RuntimeException(sprintf('Unable to create test case for test file at %s', $filename), 1, $caught);
}
return $classFQN;
}

122
src/Functions.php Normal file
View File

@ -0,0 +1,122 @@
<?php
declare(strict_types=1);
use Pest\Datasets;
use Pest\PendingObjects\AfterEachCall;
use Pest\PendingObjects\BeforeEachCall;
use Pest\PendingObjects\TestCall;
use Pest\PendingObjects\UsesCall;
use Pest\Support\Backtrace;
use Pest\Support\HigherOrderTapProxy;
use Pest\TestSuite;
use PHPUnit\Framework\TestCase;
if (!function_exists('beforeAll')) {
/**
* Runs the given closure before all tests in the current file.
*/
function beforeAll(Closure $closure): void
{
TestSuite::getInstance()->beforeAll->set($closure);
}
}
if (!function_exists('beforeEach')) {
/**
* Runs the given closure before each test in the current file.
*
* @return BeforeEachCall|TestCase|mixed
*/
function beforeEach(Closure $closure = null): BeforeEachCall
{
$filename = Backtrace::file();
return new BeforeEachCall(TestSuite::getInstance(), $filename, $closure);
}
}
if (!function_exists('dataset')) {
/**
* Registers the given dataset.
*
* @param Closure|iterable<int|string, mixed> $dataset
*/
function dataset(string $name, $dataset): void
{
Datasets::set($name, $dataset);
}
}
if (!function_exists('uses')) {
/**
* The uses function binds the given
* arguments to test closures.
*/
function uses(string ...$classAndTraits): UsesCall
{
$filename = Backtrace::file();
return new UsesCall($filename, $classAndTraits);
}
}
if (!function_exists('test')) {
/**
* Adds the given closure as a test. The first argument
* is the test description; the second argument is
* a closure that contains the test expectations.
*
* @return TestCall|TestCase|mixed
*/
function test(string $description = null, Closure $closure = null)
{
if ($description === null && TestSuite::getInstance()->test !== null) {
return new HigherOrderTapProxy(TestSuite::getInstance()->test);
}
$filename = Backtrace::testFile();
return new TestCall(TestSuite::getInstance(), $filename, $description, $closure);
}
}
if (!function_exists('it')) {
/**
* Adds the given closure as a test. The first argument
* is the test description; the second argument is
* a closure that contains the test expectations.
*
* @return TestCall|TestCase|mixed
*/
function it(string $description, Closure $closure = null): TestCall
{
$description = sprintf('it %s', $description);
return test($description, $closure);
}
}
if (!function_exists('afterEach')) {
/**
* Runs the given closure after each test in the current file.
*
* @return AfterEachCall|TestCase|mixed
*/
function afterEach(Closure $closure = null): AfterEachCall
{
$filename = Backtrace::file();
return new AfterEachCall(TestSuite::getInstance(), $filename, $closure);
}
}
if (!function_exists('afterAll')) {
/**
* Runs the given closure after all tests in the current file.
*/
function afterAll(Closure $closure): void
{
TestSuite::getInstance()->afterAll->set($closure);
}
}

View File

@ -8,6 +8,8 @@ use Illuminate\Console\Command;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Str;
use Pest\Exceptions\InvalidConsoleArgument;
use function Pest\testDirectory;
use Pest\TestSuite;
/**
* @internal
@ -19,7 +21,8 @@ final class PestDatasetCommand extends Command
*
* @var string
*/
protected $signature = 'pest:dataset {name : The name of the dataset}';
protected $signature = 'pest:dataset {name : The name of the dataset}
{--test-directory=tests : The name of the tests directory}';
/**
* The console command description.
@ -33,10 +36,13 @@ final class PestDatasetCommand extends Command
*/
public function handle(): void
{
/* @phpstan-ignore-next-line */
TestSuite::getInstance(base_path(), $this->option('test-directory'));
/** @var string $name */
$name = $this->argument('name');
$relativePath = sprintf('tests/Datasets/%s.php', ucfirst($name));
$relativePath = sprintf(testDirectory('Datasets/%s.php'), ucfirst($name));
/* @phpstan-ignore-next-line */
$target = base_path($relativePath);

View File

@ -16,7 +16,9 @@ final class PestDuskCommand extends DuskCommand
*
* @var string
*/
protected $signature = 'pest:dusk {--without-tty : Disable output to TTY}';
protected $signature = 'pest:dusk
{--browse : Open a browser instead of using headless mode}
{--without-tty : Disable output to TTY}';
/**
* The console command description.
@ -33,9 +35,9 @@ final class PestDuskCommand extends DuskCommand
protected function binary()
{
if ('phpdbg' === PHP_SAPI) {
return [PHP_BINARY, '-qrr', 'vendor/bin/pest'];
return [PHP_BINARY, '-qrr', 'vendor/pestphp/pest/bin/pest'];
}
return [PHP_BINARY, 'vendor/bin/pest'];
return [PHP_BINARY, 'vendor/pestphp/pest/bin/pest'];
}
}

View File

@ -8,7 +8,8 @@ use Illuminate\Console\Command;
use Illuminate\Support\Facades\File;
use Pest\Console\Thanks;
use Pest\Exceptions\InvalidConsoleArgument;
use Pest\Support\Str;
use function Pest\testDirectory;
use Pest\TestSuite;
/**
* @internal
@ -20,7 +21,7 @@ final class PestInstallCommand extends Command
*
* @var string
*/
protected $signature = 'pest:install';
protected $signature = 'pest:install {--test-directory=tests : The name of the tests directory}';
/**
* The console command description.
@ -35,15 +36,14 @@ final class PestInstallCommand extends Command
public function handle(): void
{
/* @phpstan-ignore-next-line */
$pest = base_path('tests/Pest.php');
/* @phpstan-ignore-next-line */
$helpers = base_path('tests/Helpers.php');
$stubs = $this->isLumen() ? 'stubs/Lumen' : 'stubs/Laravel';
TestSuite::getInstance(base_path(), $this->option('test-directory'));
foreach ([$pest, $helpers] as $file) {
if (File::exists($file)) {
throw new InvalidConsoleArgument(sprintf('%s already exist', $file));
}
/* @phpstan-ignore-next-line */
$pest = base_path(testDirectory('Pest.php'));
$stubs = 'stubs/Laravel';
if (File::exists($pest)) {
throw new InvalidConsoleArgument(sprintf('%s already exist', $pest));
}
File::copy(implode(DIRECTORY_SEPARATOR, [
@ -52,26 +52,10 @@ final class PestInstallCommand extends Command
'Pest.php',
]), $pest);
File::copy(implode(DIRECTORY_SEPARATOR, [
dirname(__DIR__, 3),
$stubs,
'Helpers.php',
]), $helpers);
$this->output->success('`tests/Pest.php` created successfully.');
$this->output->success('`tests/Helpers.php` created successfully.');
if (!(bool) $this->option('no-interaction')) {
(new Thanks($this->output))();
}
}
/**
* Determine if this is a Lumen application.
*/
private function isLumen(): bool
{
/* @phpstan-ignore-next-line */
return Str::startsWith(app()->version(), 'Lumen');
}
}

View File

@ -8,6 +8,8 @@ use Illuminate\Console\Command;
use Illuminate\Support\Facades\File;
use Pest\Exceptions\InvalidConsoleArgument;
use Pest\Support\Str;
use function Pest\testDirectory;
use Pest\TestSuite;
/**
* @internal
@ -19,7 +21,7 @@ final class PestTestCommand extends Command
*
* @var string
*/
protected $signature = 'pest:test {name : The name of the file} {--unit : Create a unit test}';
protected $signature = 'pest:test {name : The name of the file} {--unit : Create a unit test} {--dusk : Create a Dusk test} {--test-directory=tests : The name of the tests directory}';
/**
* The console command description.
@ -33,12 +35,16 @@ final class PestTestCommand extends Command
*/
public function handle(): void
{
/* @phpstan-ignore-next-line */
TestSuite::getInstance(base_path(), $this->option('test-directory'));
/** @var string $name */
$name = $this->argument('name');
$type = ((bool) $this->option('unit')) ? 'Unit' : 'Feature';
$type = ((bool) $this->option('unit')) ? 'Unit' : (((bool) $this->option('dusk')) ? 'Browser' : 'Feature');
$relativePath = sprintf('tests/%s/%s.php',
$relativePath = sprintf(
testDirectory('%s/%s.php'),
$type,
ucfirst($name)
);

430
src/Logging/JUnit.php Normal file
View File

@ -0,0 +1,430 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHPUnit.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Pest\Logging;
use function class_exists;
use DOMDocument;
use DOMElement;
use Exception;
use function get_class;
use function method_exists;
use Pest\Concerns\TestCase;
use PHPUnit\Framework\AssertionFailedError;
use PHPUnit\Framework\ExceptionWrapper;
use PHPUnit\Framework\SelfDescribing;
use PHPUnit\Framework\Test;
use PHPUnit\Framework\TestFailure;
use PHPUnit\Framework\TestListener;
use PHPUnit\Framework\TestSuite;
use PHPUnit\Framework\Warning;
use PHPUnit\Util\Filter;
use PHPUnit\Util\Printer;
use PHPUnit\Util\Xml;
use ReflectionClass;
use ReflectionException;
use function sprintf;
use function str_replace;
use Throwable;
use function trim;
/**
* @internal This class is not covered by the backward compatibility promise for PHPUnit
*/
final class JUnit extends Printer implements TestListener
{
/**
* @var DOMDocument
*/
private $document;
/**
* @var DOMElement
*/
private $root;
/**
* @var DOMElement[]
*/
private $testSuites = [];
/**
* @var int[]
*/
private $testSuiteTests = [0];
/**
* @var int[]
*/
private $testSuiteAssertions = [0];
/**
* @var int[]
*/
private $testSuiteErrors = [0];
/**
* @var int[]
*/
private $testSuiteWarnings = [0];
/**
* @var int[]
*/
private $testSuiteFailures = [0];
/**
* @var int[]
*/
private $testSuiteSkipped = [0];
/**
* @var int[]|float[]
*/
private $testSuiteTimes = [0];
/**
* @var int
*/
private $testSuiteLevel = 0;
/**
* @var DOMElement|null
*/
private $currentTestCase;
public function __construct(string $out)
{
$this->document = new DOMDocument('1.0', 'UTF-8');
$this->document->formatOutput = true;
$this->root = $this->document->createElement('testsuites');
$this->document->appendChild($this->root);
parent::__construct($out);
}
/**
* Flush buffer and close output.
*/
public function flush(): void
{
$this->write($this->getXML());
parent::flush();
}
/**
* An error occurred.
*/
public function addError(Test $test, Throwable $t, float $time): void
{
$this->doAddFault($test, $t, 'error');
$this->testSuiteErrors[$this->testSuiteLevel]++;
}
/**
* A warning occurred.
*/
public function addWarning(Test $test, Warning $e, float $time): void
{
$this->doAddFault($test, $e, 'warning');
$this->testSuiteWarnings[$this->testSuiteLevel]++;
}
/**
* A failure occurred.
*/
public function addFailure(Test $test, AssertionFailedError $e, float $time): void
{
$this->doAddFault($test, $e, 'failure');
$this->testSuiteFailures[$this->testSuiteLevel]++;
}
/**
* Incomplete test.
*/
public function addIncompleteTest(Test $test, Throwable $t, float $time): void
{
$this->doAddSkipped();
}
/**
* Risky test.
*/
public function addRiskyTest(Test $test, Throwable $t, float $time): void
{
}
/**
* Skipped test.
*/
public function addSkippedTest(Test $test, Throwable $t, float $time): void
{
$this->doAddSkipped();
}
/** @phpstan-ignore-next-line */
public function startTestSuite(TestSuite $suite): void
{
$testSuite = $this->document->createElement('testsuite');
$testSuite->setAttribute('name', $suite->getName());
if (class_exists($suite->getName(), false)) {
try {
$class = new ReflectionClass($suite->getName());
if ($class->hasMethod('__getFileName')) {
$fileName = $class->getMethod('__getFileName')->invoke(null);
} else {
$fileName = $class->getFileName();
}
$testSuite->setAttribute('file', $fileName);
} catch (ReflectionException $e) {
// @ignoreException
}
}
if ($this->testSuiteLevel > 0) {
$this->testSuites[$this->testSuiteLevel]->appendChild($testSuite);
} else {
$this->root->appendChild($testSuite);
}
$this->testSuiteLevel++;
$this->testSuites[$this->testSuiteLevel] = $testSuite;
$this->testSuiteTests[$this->testSuiteLevel] = 0;
$this->testSuiteAssertions[$this->testSuiteLevel] = 0;
$this->testSuiteErrors[$this->testSuiteLevel] = 0;
$this->testSuiteWarnings[$this->testSuiteLevel] = 0;
$this->testSuiteFailures[$this->testSuiteLevel] = 0;
$this->testSuiteSkipped[$this->testSuiteLevel] = 0;
$this->testSuiteTimes[$this->testSuiteLevel] = 0;
}
/** @phpstan-ignore-next-line */
public function endTestSuite(TestSuite $suite): void
{
$this->testSuites[$this->testSuiteLevel]->setAttribute(
'tests',
(string) $this->testSuiteTests[$this->testSuiteLevel]
);
$this->testSuites[$this->testSuiteLevel]->setAttribute(
'assertions',
(string) $this->testSuiteAssertions[$this->testSuiteLevel]
);
$this->testSuites[$this->testSuiteLevel]->setAttribute(
'errors',
(string) $this->testSuiteErrors[$this->testSuiteLevel]
);
$this->testSuites[$this->testSuiteLevel]->setAttribute(
'warnings',
(string) $this->testSuiteWarnings[$this->testSuiteLevel]
);
$this->testSuites[$this->testSuiteLevel]->setAttribute(
'failures',
(string) $this->testSuiteFailures[$this->testSuiteLevel]
);
$this->testSuites[$this->testSuiteLevel]->setAttribute(
'skipped',
(string) $this->testSuiteSkipped[$this->testSuiteLevel]
);
$this->testSuites[$this->testSuiteLevel]->setAttribute(
'time',
sprintf('%F', $this->testSuiteTimes[$this->testSuiteLevel])
);
if ($this->testSuiteLevel > 1) {
$this->testSuiteTests[$this->testSuiteLevel - 1] += $this->testSuiteTests[$this->testSuiteLevel];
$this->testSuiteAssertions[$this->testSuiteLevel - 1] += $this->testSuiteAssertions[$this->testSuiteLevel];
$this->testSuiteErrors[$this->testSuiteLevel - 1] += $this->testSuiteErrors[$this->testSuiteLevel];
$this->testSuiteWarnings[$this->testSuiteLevel - 1] += $this->testSuiteWarnings[$this->testSuiteLevel];
$this->testSuiteFailures[$this->testSuiteLevel - 1] += $this->testSuiteFailures[$this->testSuiteLevel];
$this->testSuiteSkipped[$this->testSuiteLevel - 1] += $this->testSuiteSkipped[$this->testSuiteLevel];
$this->testSuiteTimes[$this->testSuiteLevel - 1] += $this->testSuiteTimes[$this->testSuiteLevel];
}
$this->testSuiteLevel--;
}
/**
* A test started.
*
* @param Test|TestCase $test
*/
public function startTest(Test $test): void
{
$usesDataprovider = false;
if (method_exists($test, 'usesDataProvider')) {
$usesDataprovider = $test->usesDataProvider();
}
$testCase = $this->document->createElement('testcase');
$testCase->setAttribute('name', $test->getName());
try {
$class = new ReflectionClass($test);
// @codeCoverageIgnoreStart
} catch (ReflectionException $e) {
// @phpstan-ignore-next-line
throw new Exception($e->getMessage(), (int) $e->getCode(), $e);
}
// @codeCoverageIgnoreEnd
$methodName = $test->getName(!$usesDataprovider);
if ($class->hasMethod($methodName)) {
try {
$method = $class->getMethod($methodName);
// @codeCoverageIgnoreStart
} catch (ReflectionException $e) {
// @phpstan-ignore-next-line
throw new Exception($e->getMessage(), (int) $e->getCode(), $e);
}
// @codeCoverageIgnoreEnd
$testCase->setAttribute('class', $class->getName());
$testCase->setAttribute('classname', str_replace('\\', '.', $class->getName()));
$fileName = $class->getFileName();
if ($fileName !== false) {
$testCase->setAttribute('file', $fileName);
}
$testCase->setAttribute('line', (string) $method->getStartLine());
}
if (TeamCity::isPestTest($test)) {
$testCase->setAttribute('class', $test->getPrintableTestCaseName());
$testCase->setAttribute('classname', str_replace('\\', '.', $test->getPrintableTestCaseName()));
// @phpstan-ignore-next-line
$testCase->setAttribute('file', $test->__getFileName());
}
$this->currentTestCase = $testCase;
}
/**
* A test ended.
*/
public function endTest(Test $test, float $time): void
{
$numAssertions = 0;
if (method_exists($test, 'getNumAssertions')) {
$numAssertions = $test->getNumAssertions();
}
$this->testSuiteAssertions[$this->testSuiteLevel] += $numAssertions;
if ($this->currentTestCase !== null) {
$this->currentTestCase->setAttribute(
'assertions',
(string) $numAssertions
);
$this->currentTestCase->setAttribute(
'time',
sprintf('%F', $time)
);
$this->testSuites[$this->testSuiteLevel]->appendChild(
$this->currentTestCase
);
}
$this->testSuiteTests[$this->testSuiteLevel]++;
$this->testSuiteTimes[$this->testSuiteLevel] += $time;
$testOutput = '';
if (method_exists($test, 'hasOutput') && method_exists($test, 'getActualOutput')) {
$testOutput = $test->hasOutput() ? $test->getActualOutput() : '';
}
if ($testOutput !== '') {
$systemOut = $this->document->createElement(
'system-out',
Xml::prepareString($testOutput)
);
if ($this->currentTestCase !== null) {
$this->currentTestCase->appendChild($systemOut);
}
}
$this->currentTestCase = null;
}
/**
* Returns the XML as a string.
*/
public function getXML(): string
{
$xml = $this->document->saveXML();
if ($xml === false) {
return '';
}
return $xml;
}
private function doAddFault(Test $test, Throwable $t, string $type): void
{
if ($this->currentTestCase === null) {
return;
}
if ($test instanceof SelfDescribing) {
$buffer = $test->toString() . "\n";
} else {
$buffer = '';
}
$buffer .= trim(
TestFailure::exceptionToString($t) . "\n" .
Filter::getFilteredStacktrace($t)
);
$fault = $this->document->createElement(
$type,
Xml::prepareString($buffer)
);
if ($t instanceof ExceptionWrapper) {
$fault->setAttribute('type', $t->getClassName());
} else {
$fault->setAttribute('type', get_class($t));
}
$this->currentTestCase->appendChild($fault);
}
private function doAddSkipped(): void
{
if ($this->currentTestCase === null) {
return;
}
$skipped = $this->document->createElement('skipped');
$this->currentTestCase->appendChild($skipped);
$this->testSuiteSkipped[$this->testSuiteLevel]++;
}
}

View File

@ -2,7 +2,7 @@
declare(strict_types=1);
namespace Pest;
namespace Pest\Logging;
use function getmypid;
use Pest\Concerns\TestCase;
@ -56,7 +56,7 @@ final class TeamCity extends DefaultResultPrinter
/** @phpstan-ignore-next-line */
public function startTestSuite(TestSuite $suite): void
{
$this->flowId = getmypid();
$this->flowId = (int) getmypid();
if (!$this->isSummaryTestCountPrinted) {
$this->printEvent(
@ -121,7 +121,7 @@ final class TeamCity extends DefaultResultPrinter
$this->printEvent('testStarted', [
self::NAME => $test->getName(),
/* @phpstan-ignore-next-line */
// @phpstan-ignore-next-line
self::LOCATION_HINT => self::PROTOCOL . $test->toString(),
]);
}
@ -203,8 +203,11 @@ final class TeamCity extends DefaultResultPrinter
return (int) round($time * 1000);
}
private static function isPestTest(Test $test): bool
public static function isPestTest(Test $test): bool
{
return in_array(TestCase::class, class_uses($test), true);
/** @var array<string, string> $uses */
$uses = class_uses($test);
return in_array(TestCase::class, $uses, true);
}
}

View File

@ -1,100 +0,0 @@
<?php
declare(strict_types=1);
namespace Pest;
use PHPUnit\Framework\ExpectationFailedException;
use SebastianBergmann\Exporter\Exporter;
/**
* @internal
*
* @mixin Expectation
*/
final class OppositeExpectation
{
/**
* @var Expectation
*/
private $original;
/**
* Creates a new opposite expectation.
*/
public function __construct(Expectation $original)
{
$this->original = $original;
}
/**
* Asserts that the value array not has the provided $keys.
*
* @param array<int, int|string> $keys
*/
public function toHaveKeys(array $keys): Expectation
{
foreach ($keys as $key) {
try {
$this->original->toHaveKey($key);
} catch (ExpectationFailedException $e) {
continue;
}
$this->throwExpectationFailedExpection('toHaveKey', [$key]);
}
return $this->original;
}
/**
* Handle dynamic method calls into the original expectation.
*
* @param array<int, mixed> $arguments
*/
public function __call(string $name, array $arguments): Expectation
{
try {
/* @phpstan-ignore-next-line */
$this->original->{$name}(...$arguments);
} catch (ExpectationFailedException $e) {
return $this->original;
}
// @phpstan-ignore-next-line
$this->throwExpectationFailedExpection($name, $arguments);
}
/**
* Handle dynamic properties gets into the original expectation.
*/
public function __get(string $name): Expectation
{
try {
/* @phpstan-ignore-next-line */
$this->original->{$name};
} catch (ExpectationFailedException $e) {
return $this->original;
}
// @phpstan-ignore-next-line
$this->throwExpectationFailedExpection($name);
}
/**
* Creates a new expectation failed exception
* with a nice readable message.
*
* @param array<int, mixed> $arguments
*/
private function throwExpectationFailedExpection(string $name, array $arguments = []): void
{
$exporter = new Exporter();
$toString = function ($argument) use ($exporter): string {
return $exporter->shortenedExport($argument);
};
throw new ExpectationFailedException(sprintf('Expecting %s not %s %s.', $toString($this->original->value), strtolower((string) preg_replace('/(?<!\ )[A-Z]/', ' $0', $name)), implode(' ', array_map(function ($argument) use ($toString): string { return $toString($argument); }, $arguments))));
}
}

View File

@ -9,11 +9,10 @@ use Pest\Factories\TestCaseFactory;
use Pest\Support\Backtrace;
use Pest\Support\NullClosure;
use Pest\TestSuite;
use PHPUnit\Framework\ExecutionOrderDependency;
use SebastianBergmann\Exporter\Exporter;
/**
* @method \Pest\Expectation expect(mixed $value)
* @method \Pest\Expectations\Expectation expect(mixed $value)
*
* @internal
*/
@ -92,19 +91,9 @@ final class TestCall
*/
public function depends(string ...$tests): TestCall
{
$className = $this->testCaseFactory->getClassName();
$tests = array_map(function (string $test) use ($className): ExecutionOrderDependency {
if (strpos($test, '::') === false) {
$test = "{$className}::{$test}";
}
return new ExecutionOrderDependency($test, null, '');
}, $tests);
$this->testCaseFactory
->factoryProxies
->add(Backtrace::file(), Backtrace::line(), 'setDependencies', [$tests]);
->add(Backtrace::file(), Backtrace::line(), 'addDependencies', [$tests]);
return $this;
}

View File

@ -4,7 +4,7 @@ declare(strict_types=1);
namespace Pest\PendingObjects;
use Pest\Exceptions\InvalidUsesPath;
use Closure;
use Pest\TestSuite;
/**
@ -12,6 +12,20 @@ use Pest\TestSuite;
*/
final class UsesCall
{
/**
* Contains a global before each hook closure to be executed.
*
* Array indices here matter. They are mapped as follows:
*
* - `0` => `beforeAll`
* - `1` => `beforeEach`
* - `2` => `afterEach`
* - `3` => `afterAll`
*
* @var array<int, Closure>
*/
private $hooks = [];
/**
* Holds the class and traits.
*
@ -77,14 +91,13 @@ final class UsesCall
]);
}, $targets);
$this->targets = array_map(function ($target): string {
$isValid = is_dir($target) || file_exists($target);
if (!$isValid) {
throw new InvalidUsesPath($target);
$this->targets = array_reduce($targets, function (array $accumulator, string $target): array {
if (is_dir($target) || file_exists($target)) {
$accumulator[] = (string) realpath($target);
}
return (string) realpath($target);
}, $targets);
return $accumulator;
}, []);
}
/**
@ -97,11 +110,56 @@ final class UsesCall
return $this;
}
/**
* Sets the global beforeAll test hook.
*/
public function beforeAll(Closure $hook): UsesCall
{
$this->hooks[0] = $hook;
return $this;
}
/**
* Sets the global beforeEach test hook.
*/
public function beforeEach(Closure $hook): UsesCall
{
$this->hooks[1] = $hook;
return $this;
}
/**
* Sets the global afterEach test hook.
*/
public function afterEach(Closure $hook): UsesCall
{
$this->hooks[2] = $hook;
return $this;
}
/**
* Sets the global afterAll test hook.
*/
public function afterAll(Closure $hook): UsesCall
{
$this->hooks[3] = $hook;
return $this;
}
/**
* Dispatch the creation of uses.
*/
public function __destruct()
{
TestSuite::getInstance()->tests->use($this->classAndTraits, $this->groups, $this->targets);
TestSuite::getInstance()->tests->use(
$this->classAndTraits,
$this->groups,
$this->targets,
$this->hooks,
);
}
}

View File

@ -6,5 +6,10 @@ namespace Pest;
function version(): string
{
return '0.3.15';
return '1.3.0';
}
function testDirectory(string $file = ''): string
{
return TestSuite::getInstance()->testPath . '/' . $file;
}

View File

@ -22,7 +22,7 @@ final class Plugin
public static function uses(string ...$traits): void
{
self::$callables[] = function () use ($traits): void {
uses(...$traits)->in(TestSuite::getInstance()->rootPath . DIRECTORY_SEPARATOR . 'tests');
uses(...$traits)->in(TestSuite::getInstance()->rootPath . DIRECTORY_SEPARATOR . testDirectory());
};
}
}

View File

@ -41,7 +41,6 @@ final class AfterEachRepository
return ChainableClosure::from(function (): void {
if (class_exists(Mockery::class)) {
/* @phpstan-ignore-next-line */
if ($container = Mockery::getContainer()) {
/* @phpstan-ignore-next-line */
$this->addToAssertionCount($container->mockery_getExpectationCount());

View File

@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Pest\Repositories;
use Closure;
use Pest\Exceptions\ShouldNotHappen;
use Pest\Exceptions\TestAlreadyExist;
use Pest\Exceptions\TestCaseAlreadyInUse;
@ -24,7 +25,7 @@ final class TestRepository
private $state = [];
/**
* @var array<string, array<int, array<int, string>>>
* @var array<string, array<int, array<int, string|Closure>>>
*/
private $uses = [];
@ -46,12 +47,13 @@ final class TestRepository
};
foreach ($this->uses as $path => $uses) {
[$classOrTraits, $groups] = $uses;
$setClassName = function (TestCaseFactory $testCase, string $key) use ($path, $classOrTraits, $groups, $startsWith): void {
[$classOrTraits, $groups, $hooks] = $uses;
$setClassName = function (TestCaseFactory $testCase, string $key) use ($path, $classOrTraits, $groups, $startsWith, $hooks): void {
[$filename] = explode('@', $key);
if ((!is_dir($path) && $filename === $path) || (is_dir($path) && $startsWith($filename, $path))) {
foreach ($classOrTraits as $class) {
foreach ($classOrTraits as $class) { /** @var string $class */
if (class_exists($class)) {
if ($testCase->class !== TestCase::class) {
throw new TestCaseAlreadyInUse($testCase->class, $class, $filename);
@ -62,10 +64,12 @@ final class TestRepository
}
}
$testCase
->factoryProxies
// Consider set the real line here.
->add($filename, 0, 'addGroups', [$groups]);
// IDEA: Consider set the real lines on these.
$testCase->factoryProxies->add($filename, 0, 'addGroups', [$groups]);
$testCase->factoryProxies->add($filename, 0, 'addBeforeAll', [$hooks[0] ?? null]);
$testCase->factoryProxies->add($filename, 0, 'addBeforeEach', [$hooks[1] ?? null]);
$testCase->factoryProxies->add($filename, 0, 'addAfterEach', [$hooks[2] ?? null]);
$testCase->factoryProxies->add($filename, 0, 'addAfterAll', [$hooks[3] ?? null]);
}
};
@ -81,7 +85,7 @@ final class TestRepository
$state = count($onlyState) > 0 ? $onlyState : $this->state;
foreach ($state as $testFactory) {
/* @var TestCaseFactory $testFactory */
/** @var TestCaseFactory $testFactory */
$tests = $testFactory->build($testSuite);
foreach ($tests as $test) {
$each($test);
@ -92,11 +96,12 @@ final class TestRepository
/**
* Uses the given `$testCaseClass` on the given `$paths`.
*
* @param array<int, string> $classOrTraits
* @param array<int, string> $groups
* @param array<int, string> $paths
* @param array<int, string> $classOrTraits
* @param array<int, string> $groups
* @param array<int, string> $paths
* @param array<int, Closure> $hooks
*/
public function use(array $classOrTraits, array $groups, array $paths): void
public function use(array $classOrTraits, array $groups, array $paths, array $hooks): void
{
foreach ($classOrTraits as $classOrTrait) {
if (!class_exists($classOrTrait) && !trait_exists($classOrTrait)) {
@ -109,9 +114,10 @@ final class TestRepository
$this->uses[$path] = [
array_merge($this->uses[$path][0], $classOrTraits),
array_merge($this->uses[$path][1], $groups),
$this->uses[$path][2] + $hooks, // NOTE: array_merge will destroy numeric indices
];
} else {
$this->uses[$path] = [$classOrTraits, $groups];
$this->uses[$path] = [$classOrTraits, $groups, $hooks];
}
}
}

View File

@ -12,13 +12,28 @@ use Closure;
final class ChainableClosure
{
/**
* Calls the given `$closure` and chains the the `$next` closure.
* Calls the given `$closure` and chains the `$next` closure.
*/
public static function from(Closure $closure, Closure $next): Closure
{
return function () use ($closure, $next): void {
/* @phpstan-ignore-next-line */
call_user_func_array(Closure::bind($closure, $this, get_class($this)), func_get_args());
/* @phpstan-ignore-next-line */
call_user_func_array(Closure::bind($next, $this, get_class($this)), func_get_args());
};
}
/**
* Call the given static `$closure` and chains the `$next` closure.
*/
public static function fromStatic(Closure $closure, Closure $next): Closure
{
return static function () use ($closure, $next): void {
/* @phpstan-ignore-next-line */
call_user_func_array(Closure::bind($closure, null, self::class), func_get_args());
/* @phpstan-ignore-next-line */
call_user_func_array(Closure::bind($next, null, self::class), func_get_args());
};
}
}

View File

@ -79,6 +79,7 @@ final class Container
if ($candidate === null) {
$type = $param->getType();
/* @phpstan-ignore-next-line */
if ($type !== null && $type->isBuiltin()) {
$candidate = $param->getName();
} else {

View File

@ -1,33 +0,0 @@
<?php
declare(strict_types=1);
namespace Pest\Support;
use Closure;
final class Extendable
{
/**
* The extendable class.
*
* @var string
*/
private $extendableClass;
/**
* Creates a new extendable instance.
*/
public function __construct(string $extendableClass)
{
$this->extendableClass = $extendableClass;
}
/**
* Register a custom extend.
*/
public function extend(string $name, Closure $extend): void
{
$this->extendableClass::extend($name, $extend);
}
}

View File

@ -12,7 +12,6 @@ use ReflectionException;
use ReflectionFunction;
use ReflectionNamedType;
use ReflectionParameter;
use ReflectionProperty;
/**
* @internal

View File

@ -9,6 +9,25 @@ namespace Pest\Support;
*/
final class Str
{
/**
* Pool of alpha-numeric characters for generating (unsafe) random strings
* from.
*/
private const POOL = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
/**
* Create a (unsecure & non-cryptographically safe) random alpha-numeric
* string value.
*
* @param int $length the length of the resulting randomized string
*
* @see https://github.com/laravel/framework/blob/4.2/src/Illuminate/Support/Str.php#L240-L242
*/
public static function random(int $length = 16): string
{
return substr(str_shuffle(str_repeat(self::POOL, 5)), 0, $length);
}
/**
* Checks if the given `$target` starts with the given `$search`.
*/

View File

@ -66,6 +66,13 @@ final class TestSuite
*/
public $rootPath;
/**
* Holds the test path.
*
* @var string
*/
public $testPath;
/**
* Holds an instance of the test suite.
*
@ -76,7 +83,7 @@ final class TestSuite
/**
* Creates a new instance of the test suite.
*/
public function __construct(string $rootPath)
public function __construct(string $rootPath, string $testPath)
{
$this->beforeAll = new BeforeAllRepository();
$this->beforeEach = new BeforeEachRepository();
@ -85,15 +92,16 @@ final class TestSuite
$this->afterAll = new AfterAllRepository();
$this->rootPath = (string) realpath($rootPath);
$this->testPath = $testPath;
}
/**
* Returns the current instance of the test suite.
*/
public static function getInstance(string $rootPath = null): TestSuite
public static function getInstance(string $rootPath = null, string $testPath = null): TestSuite
{
if (is_string($rootPath)) {
self::$instance = new TestSuite($rootPath);
if (is_string($rootPath) && is_string($testPath)) {
self::$instance = new TestSuite($rootPath, $testPath);
foreach (Plugin::$callables as $callable) {
$callable();

View File

@ -1,124 +0,0 @@
<?php
declare(strict_types=1);
use Pest\Datasets;
use Pest\Expectation;
use Pest\PendingObjects\AfterEachCall;
use Pest\PendingObjects\BeforeEachCall;
use Pest\PendingObjects\TestCall;
use Pest\PendingObjects\UsesCall;
use Pest\Support\Backtrace;
use Pest\Support\Extendable;
use Pest\Support\HigherOrderTapProxy;
use Pest\TestSuite;
use PHPUnit\Framework\TestCase;
/**
* Runs the given closure before all tests in the current file.
*/
function beforeAll(Closure $closure): void
{
TestSuite::getInstance()->beforeAll->set($closure);
}
/**
* Runs the given closure before each test in the current file.
*
* @return BeforeEachCall|TestCase|mixed
*/
function beforeEach(Closure $closure = null): BeforeEachCall
{
$filename = Backtrace::file();
return new BeforeEachCall(TestSuite::getInstance(), $filename, $closure);
}
/**
* Registers the given dataset.
*
* @param Closure|iterable $dataset
*/
function dataset(string $name, $dataset): void
{
Datasets::set($name, $dataset);
}
/**
* The uses function binds the given
* arguments to test closures.
*/
function uses(string ...$classAndTraits): UsesCall
{
$filename = Backtrace::file();
return new UsesCall($filename, $classAndTraits);
}
/**
* Adds the given closure as a test. The first argument
* is the test description; the second argument is
* a closure that contains the test expectations.
*
* @return TestCall|TestCase|mixed
*/
function test(string $description = null, Closure $closure = null)
{
if ($description === null && TestSuite::getInstance()->test) {
return new HigherOrderTapProxy(TestSuite::getInstance()->test);
}
$filename = Backtrace::testFile();
return new TestCall(TestSuite::getInstance(), $filename, $description, $closure);
}
/**
* Adds the given closure as a test. The first argument
* is the test description; the second argument is
* a closure that contains the test expectations.
*
* @return TestCall|TestCase|mixed
*/
function it(string $description, Closure $closure = null): TestCall
{
$description = sprintf('it %s', $description);
return test($description, $closure);
}
/**
* Runs the given closure after each test in the current file.
*
* @return AfterEachCall|TestCase|mixed
*/
function afterEach(Closure $closure = null): AfterEachCall
{
$filename = Backtrace::file();
return new AfterEachCall(TestSuite::getInstance(), $filename, $closure);
}
/**
* Runs the given closure after all tests in the current file.
*/
function afterAll(Closure $closure = null): void
{
TestSuite::getInstance()->afterAll->set($closure);
}
/**
* Creates a new expectation.
*
* @param mixed $value the Value
*
* @return Expectation|Extendable
*/
function expect($value = null)
{
if (func_num_args() === 0) {
return new Extendable(Expectation::class);
}
return test()->expect($value);
}

10
stubs/Browser.php Normal file
View File

@ -0,0 +1,10 @@
<?php
use Laravel\Dusk\Browser;
it('has {name} page', function () {
$this->browse(function (Browser $browser) {
$browser->visit('/{name}')
->assertSee('{name}');
});
});

View File

@ -1,11 +0,0 @@
<?php
namespace Tests;
/**
* A basic assert example.
*/
function assertExample(): void
{
test()->assertTrue(true);
}

View File

@ -1,3 +1,45 @@
<?php
/*
|--------------------------------------------------------------------------
| Test Case
|--------------------------------------------------------------------------
|
| The closure you provide to your test functions is always bound to a specific PHPUnit test
| case class. By default, that class is "PHPUnit\Framework\TestCase". Of course, you may
| need to change it using the "uses()" function to bind a different classes or traits.
|
*/
uses(Tests\TestCase::class)->in('Feature');
/*
|--------------------------------------------------------------------------
| Expectations
|--------------------------------------------------------------------------
|
| When you're writing tests, you often need to check that values meet certain conditions. The
| "expect()" function gives you access to a set of "expectations" methods that you can use
| to assert different things. Of course, you may extend the Expectation API at any time.
|
*/
expect()->extend('toBeOne', function () {
return $this->toBe(1);
});
/*
|--------------------------------------------------------------------------
| Functions
|--------------------------------------------------------------------------
|
| While Pest is very powerful out-of-the-box, you may have some testing code specific to your
| project that you don't want to repeat in every file. Here you can also expose helpers as
| global functions to help you to reduce the number of lines of code in your test files.
|
*/
function something()
{
// ..
}

View File

@ -1,11 +0,0 @@
<?php
namespace Tests;
/**
* A basic assert example.
*/
function assertExample(): void
{
test()->assertTrue(true);
}

View File

@ -1,3 +0,0 @@
<?php
uses(TestCase::class)->in(__DIR__);

View File

@ -1,17 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/autoload.php"
colors="true"
>
<testsuites>
<testsuite name="Application Test Suite">
<directory suffix="Test.php">./tests</directory>
</testsuite>
</testsuites>
<coverage processUncoveredFiles="true">
<include>
<directory suffix=".php">./app</directory>
</include>
</coverage>
</phpunit>

View File

@ -2,232 +2,6 @@
PASS Tests\CustomTestCase\ExecutedTest
✓ that gets executed
PASS Tests\Expect\extend
✓ it macros true is true
✓ it macros false is not true
✓ it macros true is true with argument
✓ it macros false is not true with argument
PASS Tests\Expect\not
✓ not property calls
PASS Tests\Expect\toBe
✓ expect true → toBeTrue → and false → toBeFalse
✓ strict comparisons
✓ failures
✓ not failures
PASS Tests\Expect\toBeArray
✓ pass
✓ failures
✓ not failures
PASS Tests\Expect\toBeBool
✓ pass
✓ failures
✓ not failures
PASS Tests\Expect\toBeCallable
✓ pass
✓ failures
✓ not failures
PASS Tests\Expect\toBeDirectory
✓ pass
✓ failures
✓ not failures
PASS Tests\Expect\toBeEmpty
✓ pass
✓ failures
✓ not failures
PASS Tests\Expect\toBeFalse
✓ strict comparisons
✓ failures
✓ not failures
PASS Tests\Expect\toBeFile
✓ pass
✓ failures
✓ not failures
PASS Tests\Expect\toBeFloat
✓ pass
✓ failures
✓ not failures
PASS Tests\Expect\toBeGreatherThan
✓ passes
✓ failures
✓ not failures
PASS Tests\Expect\toBeGreatherThanOrEqual
✓ passes
✓ failures
✓ not failures
PASS Tests\Expect\toBeInfinite
✓ pass
✓ failures
✓ not failures
PASS Tests\Expect\toBeInstanceOf
✓ pass
✓ failures
✓ not failures
PASS Tests\Expect\toBeInt
✓ pass
✓ failures
✓ not failures
PASS Tests\Expect\toBeIterable
✓ pass
✓ failures
✓ not failures
PASS Tests\Expect\toBeLessThan
✓ passes
✓ failures
✓ not failures
PASS Tests\Expect\toBeLessThanOrEqual
✓ passes
✓ failures
✓ not failures
PASS Tests\Expect\toBeNAN
✓ pass
✓ failures
✓ not failures
PASS Tests\Expect\toBeNull
✓ pass
✓ failures
✓ not failures
PASS Tests\Expect\toBeNumeric
✓ pass
✓ failures
✓ not failures
PASS Tests\Expect\toBeObject
✓ pass
✓ failures
✓ not failures
PASS Tests\Expect\toBeReadableDirectory
✓ pass
✓ failures
✓ not failures
PASS Tests\Expect\toBeReadableFile
✓ pass
✓ failures
✓ not failures
PASS Tests\Expect\toBeResource
✓ pass
✓ failures
✓ not failures
PASS Tests\Expect\toBeScalar
✓ pass
✓ failures
✓ not failures
PASS Tests\Expect\toBeString
✓ pass
✓ failures
✓ not failures
PASS Tests\Expect\toBeTrue
✓ strict comparisons
✓ failures
✓ not failures
PASS Tests\Expect\toBeWritableDirectory
✓ pass
✓ failures
✓ not failures
PASS Tests\Expect\toBeWritableFile
✓ pass
✓ failures
✓ not failures
PASS Tests\Expect\toContain
✓ passes strings
✓ passes arrays
✓ failures
✓ not failures
PASS Tests\Expect\toEndWith
✓ pass
✓ failures
✓ not failures
PASS Tests\Expect\toEqual
✓ pass
✓ failures
✓ not failures
PASS Tests\Expect\toEqualCanonicalizing
✓ pass
✓ failures
✓ not failures
PASS Tests\Expect\toEqualWithDelta
✓ pass
✓ failures
✓ not failures
PASS Tests\Expect\toHaveCount
✓ pass
✓ failures
✓ not failures
PASS Tests\Expect\toHaveKey
✓ pass
✓ failures
✓ not failures
PASS Tests\Expect\toHaveKeys
✓ pass
✓ failures
✓ not failures
PASS Tests\Expect\toHaveProperty
✓ pass
✓ failures
✓ not failures
PASS Tests\Expect\toMatch
✓ pass
✓ failures
✓ not failures
PASS Tests\Expect\toMatchArray
✓ pass
✓ failures
✓ not failures
PASS Tests\Expect\toMatchConstraint
✓ pass
✓ failures
✓ not failures
PASS Tests\Expect\toMatchObject
✓ pass
✓ failures
✓ not failures
PASS Tests\Expect\toStartWith
✓ pass
✓ failures
✓ not failures
PASS Tests\Features\AfterAll
✓ deletes file after all
@ -266,8 +40,8 @@
✓ eager wrapped registered datasets with (1)
✓ eager wrapped registered datasets with (2)
✓ eager registered wrapped datasets did the job right
✓ named datasets with data set "one" (1)
✓ named datasets with data set "two" (2)
✓ named datasets with data set "one"
✓ named datasets with data set "two"
✓ named datasets did the job right
✓ lazy named datasets with (Bar Object (...))
✓ it creates unique test case names with ('Name 1', Pest\Plugin Object (), true) #1
@ -289,6 +63,7 @@
✓ it allows to call underlying protected/private methods
✓ it throws error if method do not exist
✓ it can forward unexpected calls to any global function
✓ it can use helpers from helpers file
PASS Tests\Features\HigherOrderTests
✓ it proxies calls to object
@ -302,9 +77,6 @@
✓ it can call chained macro method
✓ it will throw exception from call if no macro exists
PASS Tests\Features\Mocks
✓ it has bar
PASS Tests\Features\PendingHigherOrderTests
✓ get 'foo' → get 'bar' → expect true → toBeTrue
✓ get 'foo' → expect true → toBeTrue
@ -329,6 +101,39 @@
PASS Tests\Fixtures\ExampleTest
✓ it example 2
PASS Tests\Hooks\AfterAllTest
✓ global afterAll execution order
PASS Tests\Hooks\AfterEachTest
✓ global afterEach execution order
PASS Tests\Hooks\BeforeAllTest
✓ global beforeAll execution order
PASS Tests\Hooks\BeforeEachTest
✓ global beforeEach execution order
PASS Tests\PHPUnit\CustomAffixes\InvalidTestName
✓ it runs file names like `@#$%^&()-_=+.php`
PASS Tests\PHPUnit\CustomAffixes\ATestWithSpaces
✓ it runs file names like `A Test With Spaces.php`
PASS Tests\PHPUnit\CustomAffixes\AdditionalFileExtensionspec
✓ it runs file names like `AdditionalFileExtension.spec.php`
PASS Tests\PHPUnit\CustomAffixes\ManyExtensionsclasstest
✓ it runs file names like `ManyExtensions.class.test.php`
PASS Tests\PHPUnit\CustomAffixes\TestCaseWithQuotes
✓ it runs file names like `Test 'Case' With Quotes.php`
PASS Tests\PHPUnit\CustomAffixes\kebabcasespec
✓ it runs file names like `kebab-case-spec.php`
PASS Tests\PHPUnit\CustomAffixes\snakecasespec
✓ it runs file names like `snake_case_spec.php`
PASS Tests\PHPUnit\CustomTestCase\UsesPerDirectory
✓ closure was bound to CustomTestCase
@ -363,7 +168,8 @@
✓ it outputs the help information when --help is used
PASS Tests\Unit\Datasets
✓ it show the names of named datasets in their description
✓ it show only the names of named datasets in their description
✓ it show the actual dataset of non-named datasets in their description
PASS Tests\Unit\Plugins\Version
✓ it outputs the version when --version is used
@ -410,5 +216,9 @@
✓ depends run test only once
✓ depends works with the correct test name
Tests: 7 skipped, 242 passed
PASS Tests\Features\DependsInheritance
✓ it is a test
✓ it uses correct parent class
Tests: 7 skipped, 120 passed

View File

@ -1,29 +0,0 @@
<?php
expect()->extend('toBeAMacroExpectation', function () {
$this->toBeTrue();
return $this;
});
expect()->extend('toBeAMacroExpectationWithArguments', function (bool $value) {
$this->toBe($value);
return $this;
});
it('macros true is true', function () {
expect(true)->toBeAMacroExpectation();
});
it('macros false is not true', function () {
expect(false)->not->toBeAMacroExpectation();
});
it('macros true is true with argument', function () {
expect(true)->toBeAMacroExpectationWithArguments(true);
});
it('macros false is not true with argument', function () {
expect(false)->not->toBeAMacroExpectationWithArguments(true);
});

View File

@ -1,10 +0,0 @@
<?php
test('not property calls', function () {
expect(true)
->toBeTrue()
->not()->toBeFalse()
->not->toBeFalse
->and(false)
->toBeFalse();
});

View File

@ -1,20 +0,0 @@
<?php
use PHPUnit\Framework\ExpectationFailedException;
expect(true)->toBeTrue()->and(false)->toBeFalse();
test('strict comparisons', function () {
$nuno = new stdClass();
$dries = new stdClass();
expect($nuno)->toBe($nuno)->not->toBe($dries);
});
test('failures', function () {
expect(1)->toBe(2);
})->throws(ExpectationFailedException::class);
test('not failures', function () {
expect(1)->not->toBe(1);
})->throws(ExpectationFailedException::class);

View File

@ -1,16 +0,0 @@
<?php
use PHPUnit\Framework\ExpectationFailedException;
test('pass', function () {
expect([1, 2, 3])->toBeArray();
expect('1, 2, 3')->not->toBeArray();
});
test('failures', function () {
expect(null)->toBeArray();
})->throws(ExpectationFailedException::class);
test('not failures', function () {
expect(['a', 'b', 'c'])->not->toBeArray();
})->throws(ExpectationFailedException::class);

View File

@ -1,16 +0,0 @@
<?php
use PHPUnit\Framework\ExpectationFailedException;
test('pass', function () {
expect(true)->toBeBool();
expect(0)->not->toBeBool();
});
test('failures', function () {
expect(null)->toBeBool();
})->throws(ExpectationFailedException::class);
test('not failures', function () {
expect(false)->not->toBeBool();
})->throws(ExpectationFailedException::class);

View File

@ -1,18 +0,0 @@
<?php
use PHPUnit\Framework\ExpectationFailedException;
test('pass', function () {
expect(function () {})->toBeCallable();
expect(null)->not->toBeCallable();
});
test('failures', function () {
$hello = 5;
expect($hello)->toBeCallable();
})->throws(ExpectationFailedException::class);
test('not failures', function () {
expect(function () { return 42; })->not->toBeCallable();
})->throws(ExpectationFailedException::class);

View File

@ -1,17 +0,0 @@
<?php
use PHPUnit\Framework\ExpectationFailedException;
test('pass', function () {
$temp = sys_get_temp_dir();
expect($temp)->toBeDirectory();
});
test('failures', function () {
expect('/random/path/whatever')->toBeDirectory();
})->throws(ExpectationFailedException::class);
test('not failures', function () {
expect('.')->not->toBeDirectory();
})->throws(ExpectationFailedException::class);

View File

@ -1,18 +0,0 @@
<?php
use PHPUnit\Framework\ExpectationFailedException;
test('pass', function () {
expect([])->toBeEmpty();
expect(null)->toBeEmpty();
});
test('failures', function () {
expect([1, 2])->toBeEmpty();
expect(' ')->toBeEmpty();
})->throws(ExpectationFailedException::class);
test('not failures', function () {
expect([])->not->toBeEmpty();
expect(null)->not->toBeEmpty();
})->throws(ExpectationFailedException::class);

View File

@ -1,15 +0,0 @@
<?php
use PHPUnit\Framework\ExpectationFailedException;
test('strict comparisons', function () {
expect(false)->toBeFalse();
});
test('failures', function () {
expect('')->toBeFalse();
})->throws(ExpectationFailedException::class);
test('not failures', function () {
expect(false)->not->toBe(false);
})->throws(ExpectationFailedException::class);

View File

@ -1,23 +0,0 @@
<?php
use PHPUnit\Framework\ExpectationFailedException;
beforeEach(function () {
touch($this->tempFile = sys_get_temp_dir() . '/fake.file');
});
afterEach(function () {
unlink($this->tempFile);
});
test('pass', function () {
expect($this->tempFile)->toBeFile();
});
test('failures', function () {
expect('/random/path/whatever.file')->toBeFile();
})->throws(ExpectationFailedException::class);
test('not failures', function () {
expect($this->tempFile)->not->toBeFile();
})->throws(ExpectationFailedException::class);

View File

@ -1,16 +0,0 @@
<?php
use PHPUnit\Framework\ExpectationFailedException;
test('pass', function () {
expect(1.0)->toBeFloat();
expect(1)->not->toBeFloat();
});
test('failures', function () {
expect(42)->toBeFloat();
})->throws(ExpectationFailedException::class);
test('not failures', function () {
expect(log(3))->not->toBeFloat();
})->throws(ExpectationFailedException::class);

View File

@ -1,16 +0,0 @@
<?php
use PHPUnit\Framework\ExpectationFailedException;
test('passes', function () {
expect(42)->toBeGreaterThan(41);
expect(4)->toBeGreaterThan(3.9);
});
test('failures', function () {
expect(4)->toBeGreaterThan(4);
})->throws(ExpectationFailedException::class);
test('not failures', function () {
expect(5)->not->toBeGreaterThan(4);
})->throws(ExpectationFailedException::class);

View File

@ -1,16 +0,0 @@
<?php
use PHPUnit\Framework\ExpectationFailedException;
test('passes', function () {
expect(42)->toBeGreaterThanOrEqual(41);
expect(4)->toBeGreaterThanOrEqual(4);
});
test('failures', function () {
expect(4)->toBeGreaterThanOrEqual(4.1);
})->throws(ExpectationFailedException::class);
test('not failures', function () {
expect(5)->not->toBeGreaterThanOrEqual(5);
})->throws(ExpectationFailedException::class);

View File

@ -1,16 +0,0 @@
<?php
use PHPUnit\Framework\ExpectationFailedException;
test('pass', function () {
expect(log(0))->toBeInfinite();
expect(log(1))->not->toBeInfinite();
});
test('failures', function () {
expect(asin(2))->toBeInfinite();
})->throws(ExpectationFailedException::class);
test('not failures', function () {
expect(INF)->not->toBeInfinite();
})->throws(ExpectationFailedException::class);

View File

@ -1,16 +0,0 @@
<?php
use PHPUnit\Framework\ExpectationFailedException;
test('pass', function () {
expect(new Exception())->toBeInstanceOf(Exception::class);
expect(new Exception())->not->toBeInstanceOf(RuntimeException::class);
});
test('failures', function () {
expect(new Exception())->toBeInstanceOf(RuntimeException::class);
})->throws(ExpectationFailedException::class);
test('not failures', function () {
expect(new Exception())->not->toBeInstanceOf(Exception::class);
})->throws(ExpectationFailedException::class);

View File

@ -1,16 +0,0 @@
<?php
use PHPUnit\Framework\ExpectationFailedException;
test('pass', function () {
expect(42)->toBeInt();
expect(42.0)->not->toBeInt();
});
test('failures', function () {
expect(42.0)->toBeInt();
})->throws(ExpectationFailedException::class);
test('not failures', function () {
expect(6 * 7)->not->toBeInt();
})->throws(ExpectationFailedException::class);

View File

@ -1,23 +0,0 @@
<?php
use PHPUnit\Framework\ExpectationFailedException;
test('pass', function () {
expect([])->toBeIterable();
expect(null)->not->toBeIterable();
});
test('failures', function () {
expect(42)->toBeIterable();
})->throws(ExpectationFailedException::class);
test('not failures', function () {
function gen(): iterable
{
yield 1;
yield 2;
yield 3;
}
expect(gen())->not->toBeIterable();
})->throws(ExpectationFailedException::class);

View File

@ -1,16 +0,0 @@
<?php
use PHPUnit\Framework\ExpectationFailedException;
test('passes', function () {
expect(41)->toBeLessThan(42);
expect(4)->toBeLessThan(5);
});
test('failures', function () {
expect(4)->toBeLessThan(4);
})->throws(ExpectationFailedException::class);
test('not failures', function () {
expect(5)->not->toBeLessThan(6);
})->throws(ExpectationFailedException::class);

View File

@ -1,16 +0,0 @@
<?php
use PHPUnit\Framework\ExpectationFailedException;
test('passes', function () {
expect(41)->toBeLessThanOrEqual(42);
expect(4)->toBeLessThanOrEqual(4);
});
test('failures', function () {
expect(4)->toBeLessThanOrEqual(3.9);
})->throws(ExpectationFailedException::class);
test('not failures', function () {
expect(5)->not->toBeLessThanOrEqual(5);
})->throws(ExpectationFailedException::class);

View File

@ -1,16 +0,0 @@
<?php
use PHPUnit\Framework\ExpectationFailedException;
test('pass', function () {
expect(asin(2))->toBeNan();
expect(log(0))->not->toBeNan();
});
test('failures', function () {
expect(1)->toBeNan();
})->throws(ExpectationFailedException::class);
test('not failures', function () {
expect(acos(1.5))->not->toBeNan();
})->throws(ExpectationFailedException::class);

View File

@ -1,16 +0,0 @@
<?php
use PHPUnit\Framework\ExpectationFailedException;
test('pass', function () {
expect(null)->toBeNull();
expect('')->not->toBeNull();
});
test('failures', function () {
expect('hello')->toBeNull();
})->throws(ExpectationFailedException::class);
test('not failures', function () {
expect(null)->not->toBeNull();
})->throws(ExpectationFailedException::class);

View File

@ -1,16 +0,0 @@
<?php
use PHPUnit\Framework\ExpectationFailedException;
test('pass', function () {
expect(42)->toBeNumeric();
expect('A')->not->toBeNumeric();
});
test('failures', function () {
expect(null)->toBeNumeric();
})->throws(ExpectationFailedException::class);
test('not failures', function () {
expect(6 * 7)->not->toBeNumeric();
})->throws(ExpectationFailedException::class);

View File

@ -1,16 +0,0 @@
<?php
use PHPUnit\Framework\ExpectationFailedException;
test('pass', function () {
expect((object) ['a' => 1])->toBeObject();
expect(['a' => 1])->not->toBeObject();
});
test('failures', function () {
expect(null)->toBeObject();
})->throws(ExpectationFailedException::class);
test('not failures', function () {
expect((object) 'ciao')->not->toBeObject();
})->throws(ExpectationFailedException::class);

View File

@ -1,15 +0,0 @@
<?php
use PHPUnit\Framework\ExpectationFailedException;
test('pass', function () {
expect(sys_get_temp_dir())->toBeReadableDirectory();
});
test('failures', function () {
expect('/random/path/whatever')->toBeReadableDirectory();
})->throws(ExpectationFailedException::class);
test('not failures', function () {
expect(sys_get_temp_dir())->not->toBeReadableDirectory();
})->throws(ExpectationFailedException::class);

View File

@ -1,23 +0,0 @@
<?php
use PHPUnit\Framework\ExpectationFailedException;
beforeEach(function () {
touch($this->tempFile = sys_get_temp_dir() . '/fake.file');
});
afterEach(function () {
unlink($this->tempFile);
});
test('pass', function () {
expect($this->tempFile)->toBeReadableFile();
});
test('failures', function () {
expect('/random/path/whatever.file')->toBeReadableFile();
})->throws(ExpectationFailedException::class);
test('not failures', function () {
expect($this->tempFile)->not->toBeReadableFile();
})->throws(ExpectationFailedException::class);

View File

@ -1,22 +0,0 @@
<?php
use PHPUnit\Framework\ExpectationFailedException;
$resource = tmpfile();
afterAll(function () use ($resource) {
fclose($resource);
});
test('pass', function () use ($resource) {
expect($resource)->toBeResource();
expect(null)->not->toBeResource();
});
test('failures', function () {
expect(null)->toBeResource();
})->throws(ExpectationFailedException::class);
test('not failures', function () use ($resource) {
expect($resource)->not->toBeResource();
})->throws(ExpectationFailedException::class);

View File

@ -1,15 +0,0 @@
<?php
use PHPUnit\Framework\ExpectationFailedException;
test('pass', function () {
expect(1.1)->toBeScalar();
});
test('failures', function () {
expect(null)->toBeScalar();
})->throws(ExpectationFailedException::class);
test('not failures', function () {
expect(42)->not->toBeScalar();
})->throws(ExpectationFailedException::class);

View File

@ -1,16 +0,0 @@
<?php
use PHPUnit\Framework\ExpectationFailedException;
test('pass', function () {
expect('1.1')->toBeString();
expect(1.1)->not->toBeString();
});
test('failures', function () {
expect(null)->toBeString();
})->throws(ExpectationFailedException::class);
test('not failures', function () {
expect('42')->not->toBeString();
})->throws(ExpectationFailedException::class);

View File

@ -1,15 +0,0 @@
<?php
use PHPUnit\Framework\ExpectationFailedException;
test('strict comparisons', function () {
expect(true)->toBeTrue();
});
test('failures', function () {
expect('')->toBeTrue();
})->throws(ExpectationFailedException::class);
test('not failures', function () {
expect(false)->not->toBe(false);
})->throws(ExpectationFailedException::class);

View File

@ -1,15 +0,0 @@
<?php
use PHPUnit\Framework\ExpectationFailedException;
test('pass', function () {
expect(sys_get_temp_dir())->toBeWritableDirectory();
});
test('failures', function () {
expect('/random/path/whatever')->toBeWritableDirectory();
})->throws(ExpectationFailedException::class);
test('not failures', function () {
expect(sys_get_temp_dir())->not->toBeWritableDirectory();
})->throws(ExpectationFailedException::class);

View File

@ -1,23 +0,0 @@
<?php
use PHPUnit\Framework\ExpectationFailedException;
beforeEach(function () {
touch($this->tempFile = sys_get_temp_dir() . '/fake.file');
});
afterEach(function () {
unlink($this->tempFile);
});
test('pass', function () {
expect($this->tempFile)->toBeWritableFile();
});
test('failures', function () {
expect('/random/path/whatever.file')->toBeWritableFile();
})->throws(ExpectationFailedException::class);
test('not failures', function () {
expect($this->tempFile)->not->toBeWritableFile();
})->throws(ExpectationFailedException::class);

View File

@ -1,19 +0,0 @@
<?php
use PHPUnit\Framework\ExpectationFailedException;
test('passes strings', function () {
expect([1, 2, 42])->toContain(42);
});
test('passes arrays', function () {
expect('Nuno')->toContain('Nu');
});
test('failures', function () {
expect([1, 2, 42])->toContain(3);
})->throws(ExpectationFailedException::class);
test('not failures', function () {
expect([1, 2, 42])->not->toContain(42);
})->throws(ExpectationFailedException::class);

View File

@ -1,15 +0,0 @@
<?php
use PHPUnit\Framework\ExpectationFailedException;
test('pass', function () {
expect('username')->toEndWith('name');
});
test('failures', function () {
expect('username')->toEndWith('password');
})->throws(ExpectationFailedException::class);
test('not failures', function () {
expect('username')->not->toEndWith('name');
})->throws(ExpectationFailedException::class);

View File

@ -1,15 +0,0 @@
<?php
use PHPUnit\Framework\ExpectationFailedException;
test('pass', function () {
expect('00123')->toEqual(123);
});
test('failures', function () {
expect(['a', 'b', 'c'])->toEqual(['a', 'b']);
})->throws(ExpectationFailedException::class);
test('not failures', function () {
expect('042')->not->toEqual(42);
})->throws(ExpectationFailedException::class);

View File

@ -1,16 +0,0 @@
<?php
use PHPUnit\Framework\ExpectationFailedException;
test('pass', function () {
expect([1, 2, 3])->toEqualCanonicalizing([3, 1, 2]);
expect(['g', 'a', 'z'])->not->toEqualCanonicalizing(['a', 'z']);
});
test('failures', function () {
expect([3, 2, 1])->toEqualCanonicalizing([1, 2]);
})->throws(ExpectationFailedException::class);
test('not failures', function () {
expect(['a', 'b', 'c'])->not->toEqualCanonicalizing(['b', 'a', 'c']);
})->throws(ExpectationFailedException::class);

View File

@ -1,15 +0,0 @@
<?php
use PHPUnit\Framework\ExpectationFailedException;
test('pass', function () {
expect(1.0)->toEqualWithDelta(1.3, .4);
});
test('failures', function () {
expect(1.0)->toEqualWithDelta(1.5, .1);
})->throws(ExpectationFailedException::class);
test('not failures', function () {
expect(1.0)->not->toEqualWithDelta(1.6, .7);
})->throws(ExpectationFailedException::class);

View File

@ -1,15 +0,0 @@
<?php
use PHPUnit\Framework\ExpectationFailedException;
test('pass', function () {
expect([1, 2, 3])->toHaveCount(3);
});
test('failures', function () {
expect([1, 2, 3])->toHaveCount(4);
})->throws(ExpectationFailedException::class);
test('not failures', function () {
expect([1, 2, 3])->not->toHaveCount(3);
})->throws(ExpectationFailedException::class);

View File

@ -1,15 +0,0 @@
<?php
use PHPUnit\Framework\ExpectationFailedException;
test('pass', function () {
expect(['a' => 1, 'b', 'c' => 'world'])->toHaveKey('c');
});
test('failures', function () {
expect(['a' => 1, 'b', 'c' => 'world'])->toHaveKey('hello');
})->throws(ExpectationFailedException::class);
test('not failures', function () {
expect(['a' => 1, 'hello' => 'world', 'c'])->not->toHaveKey('hello');
})->throws(ExpectationFailedException::class);

View File

@ -1,15 +0,0 @@
<?php
use PHPUnit\Framework\ExpectationFailedException;
test('pass', function () {
expect(['a' => 1, 'b', 'c' => 'world'])->toHaveKeys(['a', 'c']);
});
test('failures', function () {
expect(['a' => 1, 'b', 'c' => 'world'])->toHaveKeys(['a', 'd']);
})->throws(ExpectationFailedException::class);
test('not failures', function () {
expect(['a' => 1, 'hello' => 'world', 'c'])->not->toHaveKeys(['hello', 'c']);
})->throws(ExpectationFailedException::class);

View File

@ -1,22 +0,0 @@
<?php
use PHPUnit\Framework\ExpectationFailedException;
$obj = new stdClass();
$obj->foo = 'bar';
$obj->fooNull = null;
test('pass', function () use ($obj) {
expect($obj)->toHaveProperty('foo');
expect($obj)->toHaveProperty('foo', 'bar');
expect($obj)->toHaveProperty('fooNull');
expect($obj)->toHaveProperty('fooNull', null);
});
test('failures', function () use ($obj) {
expect($obj)->toHaveProperty('bar');
})->throws(ExpectationFailedException::class);
test('not failures', function () use ($obj) {
expect($obj)->not->toHaveProperty('foo');
})->throws(ExpectationFailedException::class);

View File

@ -1,15 +0,0 @@
<?php
use PHPUnit\Framework\ExpectationFailedException;
test('pass', function () {
expect('Hello World')->toMatch('/^hello wo.*$/i');
});
test('failures', function () {
expect('Hello World')->toMatch('/^hello$/i');
})->throws(ExpectationFailedException::class);
test('not failures', function () {
expect('Hello World')->not->toMatch('/^hello wo.*$/i');
})->throws(ExpectationFailedException::class);

View File

@ -1,31 +0,0 @@
<?php
use PHPUnit\Framework\ExpectationFailedException;
beforeEach(function () {
$this->user = [
'id' => 1,
'name' => 'Nuno',
'email' => 'enunomaduro@gmail.com',
];
});
test('pass', function () {
expect($this->user)->toMatchArray([
'name' => 'Nuno',
'email' => 'enunomaduro@gmail.com',
]);
});
test('failures', function () {
expect($this->user)->toMatchArray([
'name' => 'Not the same name',
'email' => 'enunomaduro@gmail.com',
]);
})->throws(ExpectationFailedException::class);
test('not failures', function () {
expect($this->user)->not->toMatchArray([
'id' => 1,
]);
})->throws(ExpectationFailedException::class);

View File

@ -1,16 +0,0 @@
<?php
use PHPUnit\Framework\Constraint\IsTrue;
use PHPUnit\Framework\ExpectationFailedException;
test('pass', function () {
expect(true)->toMatchConstraint(new IsTrue());
});
test('failures', function () {
expect(false)->toMatchConstraint(new IsTrue());
})->throws(ExpectationFailedException::class);
test('not failures', function () {
expect(true)->not->toMatchConstraint(new IsTrue());
})->throws(ExpectationFailedException::class);

View File

@ -1,31 +0,0 @@
<?php
use PHPUnit\Framework\ExpectationFailedException;
beforeEach(function () {
$this->user = (object) [
'id' => 1,
'name' => 'Nuno',
'email' => 'enunomaduro@gmail.com',
];
});
test('pass', function () {
expect($this->user)->toMatchObject([
'name' => 'Nuno',
'email' => 'enunomaduro@gmail.com',
]);
});
test('failures', function () {
expect($this->user)->toMatchObject([
'name' => 'Not the same name',
'email' => 'enunomaduro@gmail.com',
]);
})->throws(ExpectationFailedException::class);
test('not failures', function () {
expect($this->user)->not->toMatchObject([
'id' => 1,
]);
})->throws(ExpectationFailedException::class);

View File

@ -1,15 +0,0 @@
<?php
use PHPUnit\Framework\ExpectationFailedException;
test('pass', function () {
expect('username')->toStartWith('user');
});
test('failures', function () {
expect('username')->toStartWith('password');
})->throws(ExpectationFailedException::class);
test('not failures', function () {
expect('username')->not->toStartWith('user');
})->throws(ExpectationFailedException::class);

View File

@ -6,7 +6,7 @@ beforeEach(function () use ($state) {
$this->state = $state;
});
afterEach(function () use ($state) {
afterEach(function () {
$this->state->bar = 2;
});

View File

@ -116,7 +116,7 @@ $namedDatasets = [
new Bar(),
];
test('lazy named datasets', function ($text) use ($state, $datasets) {
test('lazy named datasets', function ($text) {
expect(true)->toBeTrue();
})->with($namedDatasets);

View File

@ -0,0 +1,22 @@
<?php
use PHPUnit\Framework\TestCase;
class InheritanceTest extends TestCase
{
public function foo()
{
return 'bar';
}
}
uses(InheritanceTest::class);
it('is a test', function () {
expect(true)->toBeTrue();
});
it('uses correct parent class', function () {
expect(get_parent_class($this))->toEqual(InheritanceTest::class);
expect($this->foo())->toEqual('bar');
})->depends('it is a test');

View File

@ -42,3 +42,5 @@ it('throws error if method do not exist', function () {
})->throws(\ReflectionException::class, 'Call to undefined method PHPUnit\Framework\TestCase::name()');
it('can forward unexpected calls to any global function')->_assertThat();
it('can use helpers from helpers file')->myAssertTrue(true);

View File

@ -1,17 +0,0 @@
<?php
use function Tests\mock;
interface Foo
{
public function bar(): int;
}
it('has bar', function () {
$mock = mock(Foo::class);
$mock->shouldReceive('bar')
->times(1)
->andReturn(2);
$mock->bar();
});

View File

@ -1,11 +1,6 @@
<?php
namespace Tests;
use Mockery;
use Mockery\MockInterface;
function mock(string $class): MockInterface
function myAssertTrue($value)
{
return Mockery::mock($class);
test()->assertTrue($value);
}

Some files were not shown because too many files have changed in this diff Show More