Compare commits

...

161 Commits

Author SHA1 Message Date
af6240b4ee release: v1.22.1 2022-08-29 11:42:13 +01:00
78b328e970 release: v1.22.1 2022-08-29 11:42:07 +01:00
c92fa45ad7 Adds tests on PHP 8.2 2022-08-29 11:35:36 +01:00
d050ef8d54 Fixes support for PHP 8.2 2022-08-29 11:29:41 +01:00
1c2f1facac release: v1.22.0 2022-08-28 22:32:39 +01:00
8e695d62e9 Merge pull request #563 from fabio-ivona/1.x-dynamic-properties
[1.x] Dynamic properties handling
2022-08-25 13:22:45 +01:00
b9a301a13e ignore dynamic properties deprecation 2022-08-25 13:27:24 +02:00
4b9da9e3c1 Merge pull request #562 from fabio-ivona/1.x-dynamic-properties
Fix tests
2022-08-25 12:50:09 +02:00
239fceab9e lint 2022-08-25 09:46:32 +02:00
bedf033655 lint 2022-08-25 09:32:48 +02:00
66f69617f1 release: 1.21.3 2022-05-12 20:10:25 +01:00
5721e8c29a docs: updates release 2022-05-12 20:06:47 +01:00
546d19fd84 chore: cs 2022-05-12 20:03:22 +01:00
c1b32b9ffb fix: debug in high order tests 2022-05-12 20:03:11 +01:00
298b1e6784 fix: update branch alias for 1.x branch 2022-03-16 09:24:51 +00:00
63f009fadf release: v1.21.2 2022-03-05 19:34:40 +00:00
1066c2270d Merge pull request #487 from fabio-ivona/fix-throws-missing-exception
Fix throws missing exception
2022-03-04 08:57:50 +00:00
984f237a92 phpstan 2022-03-03 15:45:41 +01:00
dccd8239dd handles toThrow exception with a "class not found" error 2022-02-18 18:01:09 +01:00
e103623ecb adds failing test 2022-02-18 18:00:55 +01:00
542fc046d2 Merge pull request #462 from pestphp/revert-461-json_fix
Revert "Fixes `json` in Higher Order Expectations"
2022-01-21 16:48:35 +00:00
4720e0655b Revert "Fixes json in Higher Order Expectations" 2022-01-21 16:48:26 +00:00
a533772fe2 Merge pull request #461 from pestphp/json_fix
Fixes `json` in Higher Order Expectations
2022-01-21 16:46:30 +00:00
2218a0c137 Tweaks 2022-01-21 16:39:10 +00:00
2831629f1b Fixes bug with json method call in Higher Order Expectations. 2022-01-21 16:35:21 +00:00
1b7f1dc5b3 chore(deps): update nunomaduro/collision to ^5.10
This ensures that the minimum dependencies work with PHP 8.1
2021-12-13 20:57:27 +00:00
92b8d32ef7 release: v1.21.1 2021-11-25 16:44:17 +00:00
2969c7a5e3 Merge pull request #442 from pestphp/fix_for_sequence
[1.x] Fix for sequence
2021-11-25 16:40:08 +00:00
63c1faa9f4 Prevents issues with callables in sequence 2021-11-25 16:37:23 +00:00
b5b14ef280 chore: updates changelog branch to 1.x 2021-11-17 11:03:56 +00:00
11eb1903c2 release: v1.21.0 2021-11-17 10:54:00 +00:00
a110848f9b docs: updates changelog 2021-11-17 10:47:28 +00:00
da5c21de8f Update README.md 2021-11-09 01:42:10 +00:00
408ae4cad8 docs: adds spatie.be as platinum sponsor 2021-11-08 18:31:48 +00:00
fc2484a28a Update README.md 2021-11-05 12:00:56 +01:00
de46ee0f64 fix: adds link to xdebug coverage mode 2021-10-10 15:33:27 +01:00
1e011c7b40 fix: warns about xdebug modes 2021-10-10 15:25:02 +01:00
04dcebf3aa Revert "Merge pull request #413 from def-studio/fix_skipping_tests_with_exception_asserting"
This reverts commit e853792a59, reversing
changes made to 205238fcbf.
2021-10-10 14:40:07 +01:00
e853792a59 Merge pull request #413 from def-studio/fix_skipping_tests_with_exception_asserting
Fix skipping tests with exception asserting
2021-10-02 12:01:04 +01:00
b0fbe54181 removes duplicated test 2021-10-02 09:15:52 +02:00
2a649bdfc0 revert previous solution and invert chain and proxy calls in TestCaseFactory.php 2021-10-02 09:09:47 +02:00
e042bf7d3a removes expection expectations if test is marked as skipped 2021-10-01 15:52:45 +02:00
3ff71a4563 adds failing test 2021-10-01 15:52:02 +02:00
205238fcbf docs: updates release process 2021-09-25 13:56:06 +01:00
ba06c5a76d release: v1.20.0 2021-09-25 13:52:12 +01:00
78ffc491e9 chore: exclude builds on PHP 8.1 on mac os or windows 2021-09-25 13:37:06 +01:00
7f38de11b7 refactor: --ci option 2021-09-25 13:29:11 +01:00
a6e34d204c Merge pull request #405 from def-studio/add-new-ci-option-to-pest-bin
Add new --ci option to pest bin
2021-09-25 13:11:05 +01:00
66d47e4922 fix: usage on PHP 7.3 2021-09-25 09:39:17 +01:00
7d70b6e95a merge from master 2021-09-25 09:04:26 +02:00
076dcab4c5 Merge remote-tracking branch 'origin/master' into add-new-ci-option-to-pest-bin
# Conflicts:
#	tests/.snapshots/success.txt
2021-09-25 09:04:07 +02:00
6f42e336c9 merge conflict 2021-09-25 08:57:55 +02:00
0d72b5197c merge conflict 2021-09-25 08:55:57 +02:00
7691e3c602 Merge pull request #399 from mertasan/sequence
Fix: `sequence()` can generate false positives
2021-09-24 22:15:49 +01:00
b43a59868d feat: adds unless expectation 2021-09-24 22:12:08 +01:00
457972716f refactor: expectation match method 2021-09-24 22:02:18 +01:00
ae029660e3 tests: fixes snapshots 2021-09-24 21:18:30 +01:00
dc12419078 Merge pull request #407 from mertasan/method-match
Adds new `match()` method
2021-09-24 21:16:48 +01:00
f0ddd10a54 Merge branch 'master' into method-match 2021-09-24 21:16:43 +01:00
4daf7ee4ab refactor: throwsIf method 2021-09-24 21:15:31 +01:00
d60f320382 Merge pull request #371 from mertasan/throwsif
Add `throwsIf` exception
2021-09-24 21:10:56 +01:00
3c3e6b160b refactor: expectation when 2021-09-24 21:10:02 +01:00
c99f8f196e lint 2021-09-24 14:42:26 +03:00
9cc4ecd5ab Merge branch 'master' into method-match 2021-09-24 14:39:21 +03:00
8d96f975e0 update snapshots 2021-09-24 14:37:01 +03:00
7f214f9e12 use and() instead of new self 2021-09-24 13:59:00 +03:00
da258fa89f remove the warning 2021-09-24 13:55:16 +03:00
f23f857903 Merge pull request #406 from mertasan/method-when
Adds new `when()` method
2021-09-24 11:23:21 +01:00
fec11928cf update snapshots 2021-09-23 06:57:07 +03:00
f6131d042b add tests
The filename is not accepted as `match.php`
2021-09-23 06:56:47 +03:00
543b9542ae add new match() method 2021-09-23 06:55:28 +03:00
1681c1f4f8 update snapshots 2021-09-23 03:35:56 +03:00
f41c3ce9ba add tests 2021-09-23 03:35:46 +03:00
847b06e558 add when() method 2021-09-23 03:35:41 +03:00
601c4b01fc refactors to use a Plugin to parse --ci option 2021-09-22 14:53:16 +02:00
05c1c82ae2 lint 2021-09-22 11:05:04 +02:00
1bde49b3c4 types fix 2021-09-22 11:00:41 +02:00
b22f5e0c85 adds test for CI env runs 2021-09-22 10:38:21 +02:00
dd643faa5c adds a new --ci option to pest binary 2021-09-22 10:17:44 +02:00
facbf05016 docs: adds missing entry to changelog 2021-09-20 19:38:02 +01:00
58cff003d8 release: v1.19.0 2021-09-20 19:30:58 +01:00
ae997e6eee No need for --ignore-platform-req=php 2021-09-20 19:28:43 +01:00
e6c7d68def Adds php-81 support 2021-09-20 19:26:47 +01:00
0b19672963 Merge branch 'master' into throwsif 2021-09-19 01:47:07 +03:00
a16a19e121 update snapshots 2021-09-16 18:10:15 +03:00
3dd10b3c7c change test 2021-09-16 18:09:59 +03:00
12e63c7376 add new assertion 2021-09-16 18:09:45 +03:00
2f0cd7a4e3 Adds Fathom Analytics as sponsor 2021-09-15 10:03:05 +01:00
447af55e7c Merge pull request #391 from gabbanaesteban/master
Add `toHaveProperties`
2021-09-03 09:52:39 +01:00
253e9d10c8 Fix types 2021-09-03 04:44:10 -04:00
536ce1eca0 Update snapshots 2021-09-03 04:40:48 -04:00
4331b2aaf6 Add toHaveProperties 2021-09-03 04:26:57 -04:00
8d99cacc95 Update README.md 2021-09-02 23:36:02 +01:00
3c38facc8a Update README.md 2021-09-02 23:35:51 +01:00
ed389d35d0 Adds Auth0 as sponsor 2021-09-02 23:35:40 +01:00
60c0636523 release: v1.18.0 2021-08-30 00:05:26 +01:00
16b6f96b47 Merge pull request #389 from dansysanalyst/toHaveLength-mblen
Use mb_strlen instead of grapheme_strlen
2021-08-29 12:18:38 +01:00
042f2ec3f3 Use mb_strlen instead of grapheme_strlen
Due to inconsistent behave, mb_strlen will be used.
2021-08-29 12:55:37 +02:00
851ce36010 Merge pull request #386 from dansysanalyst/expectation-toHaveLength
Adds toHaveLength() Expectation & Tests
2021-08-29 00:45:39 +01:00
4f386894bd Revert "Use toHaveCount"
This reverts commit 2289adade2.
2021-08-28 18:14:55 +02:00
2289adade2 Use toHaveCount
changes to toHaveCount
2021-08-28 17:54:29 +02:00
29e21e3814 Merge pull request #385 from dansysanalyst/snapshot-contrib
Update snapshots
2021-08-28 16:47:25 +01:00
8367af22e7 Adds toHaveLength() Expectation & Tests
Adds toHaveLength() Expectation and its tests. toHaveLength checks if the given value has the informed length. Works with strings, array, object and collections.
2021-08-28 17:02:09 +02:00
e3d678dc04 Update snapshots
Adds update:snapshots to CONTRIBUTING
2021-08-28 16:30:17 +02:00
4ae482c707 feat: adds support for nunomaduro/collision:^6.0 2021-08-27 22:32:18 +01:00
075c31bc78 release: v1.17.0 2021-08-26 21:17:03 +01:00
2125bf9668 chore: adjusts tests 2021-08-26 21:14:56 +01:00
dbf3c0a8cf Merge pull request #361 from kbond/throw-expectation
Add `toThrow` expectation
2021-08-26 21:02:38 +01:00
eca5f89e59 release: v1.16.0 2021-08-19 17:07:45 +01:00
0b0beac122 Script update 2021-08-16 22:12:13 +01:00
578e97123d Changes -P to -p 2021-08-13 22:09:55 +01:00
01d672d563 Merge pull request #369 from pestphp/parallel
Parallel testing support
2021-08-13 16:00:26 +01:00
490b2d66e5 Fix 2021-08-13 13:41:00 +01:00
0368c4846f Adds a composer test:parallel script 2021-08-13 13:40:07 +01:00
4dfc02c5da Refactor 2021-08-13 11:19:03 +01:00
5c84b0c6d3 The getFilenames method now respects only calls. 2021-08-13 11:07:52 +01:00
b6c06e8c30 Bugfix 2021-08-13 10:46:07 +01:00
c6435d5606 Adds a helpful message for users trying to run parallel without the plugin 2021-08-13 10:44:11 +01:00
2887d212e3 CS fix 2021-08-13 10:30:26 +01:00
cadae52d5d Updates snapshots 2021-08-13 10:29:21 +01:00
d9749ca65b Adds a method for getting all filenames 2021-08-13 10:26:38 +01:00
c2070cd99d Refactor 2021-08-13 10:19:10 +01:00
03d34e9a10 Removes isInParallel 2021-08-13 10:14:01 +01:00
45e76a6df6 Typehint updates 2021-08-13 09:41:45 +01:00
28dd3c2a03 Adds -P as a parallel shortcut 2021-08-13 09:39:56 +01:00
5de981d923 Updates composer.json 2021-08-13 09:12:56 +01:00
a55b31e7c3 Update FUNDING.yml 2021-08-12 21:41:42 +01:00
5f0bd8180e Update FUNDING.yml 2021-08-12 21:41:05 +01:00
e1f1fcccbe Removes unneeded dependency 2021-08-11 21:25:41 +01:00
ab04aef561 Refactors addOutput 2021-08-11 20:59:19 +01:00
79ddb1f58e Composer change 2021-08-11 15:57:49 +01:00
c7a2e68941 Fixes a problem with the PhpUnit logger 2021-08-10 14:57:36 +01:00
5c592928d4 Adds a new method, isInParallel, to the Testable trait to allow a test to determine its parallel status. 2021-08-10 14:34:10 +01:00
bcab4224fb Skips a test that doesn't support parallel. 2021-08-10 14:24:34 +01:00
892f70b5b5 Refactor 2021-08-10 13:17:11 +01:00
5c7de5ad75 Refactor 2021-08-10 12:04:15 +01:00
995088b522 Refactor 2021-08-10 11:32:43 +01:00
ef503646ee Removes parallel classes. 2021-08-10 11:30:48 +01:00
a760470e48 Adds Pest output to parallel. 2021-08-09 18:57:20 +01:00
1d4c1a5359 update snapshots 2021-08-07 08:22:31 +03:00
8e32b88fc8 add tests 2021-08-07 08:22:26 +03:00
1a7baad338 add throwsIf exception 2021-08-07 08:22:16 +03:00
31d1b1b91d Adds a little more spacing above the coverage output. 2021-08-06 15:15:09 +01:00
7524c80af6 Merge pull request #370 from nuernbergerA/parallel
Parallel additions
2021-08-06 15:04:51 +01:00
721d5134b7 replace str_starts_with to support pre php 8.0 2021-08-06 16:00:03 +02:00
0b5321fdd7 only check for coverage driver if option is present 2021-08-06 15:48:38 +02:00
c86058fed1 use support class 2021-08-06 15:39:19 +02:00
8b295b5e9d remove debug statements 2021-08-06 15:38:59 +02:00
221248e691 introduced argument mapping, added pest coverage 2021-08-06 15:25:28 +02:00
7621247bb7 Adds workflows for parallel 2021-08-05 17:45:50 +01:00
463a50ebd4 Investigating bug fix 2021-08-05 17:39:23 +01:00
62aabc6ae1 Bugfix. The TestCase is now aware of if it is running in parallel or not 😎 2021-08-05 17:09:45 +01:00
1ca9aa5ca6 Further cleanup 2021-08-05 16:43:09 +01:00
7a76f8dce2 Further cleanup 2021-08-05 16:21:06 +01:00
beca27599c Further cleanup 2021-08-05 16:18:47 +01:00
256b167eaf Improvements 2021-08-05 15:31:09 +01:00
5526d4c24d Cleanup 2021-08-05 13:51:55 +01:00
7ea138c640 CS fixes 2021-08-05 13:41:04 +01:00
c4a659c3b5 Composer changes 2021-08-05 13:16:29 +01:00
0a3991c314 Initial working draft 2021-08-05 13:13:53 +01:00
c776bcf86d add toThrow expectation 2021-08-01 12:33:09 -04:00
56 changed files with 1553 additions and 127 deletions

View File

@ -2,12 +2,12 @@ name: Changelog
on:
push:
branches: [ master ]
branches: [ 1.x ]
paths:
- CHANGELOG.md
- .github/workflows/changelog.yml
pull_request:
branches: [ master ]
branches: [ 1.x ]
paths:
- CHANGELOG.md
jobs:

View File

@ -8,10 +8,20 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
php: ['7.3', '7.4', '8.0']
php: ['7.3', '7.4', '8.0', '8.1', '8.2']
dependency-version: [prefer-lowest, prefer-stable]
parallel: ['', '--parallel']
exclude:
- php: 8.1
os: macos-latest
- php: 8.1
os: windows-latest
- php: 8.2
os: macos-latest
- php: 8.2
os: windows-latest
name: PHP ${{ matrix.php }} - ${{ matrix.os }} - ${{ matrix.dependency-version }}
name: PHP ${{ matrix.php }} - ${{ matrix.os }} - ${{ matrix.dependency-version }} - ${{ matrix.parallel }}
steps:
- name: Checkout
@ -29,16 +39,11 @@ jobs:
echo "::add-matcher::${{ runner.tool_cache }}/php.json"
echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
- name: Install PHP 7 dependencies
- name: Install PHP dependencies
run: composer update --${{ matrix.dependency-version }} --no-interaction --no-progress
if: "matrix.php < 8"
- name: Install PHP 8 dependencies
run: composer update --${{ matrix.dependency-version }} --ignore-platform-req=php --no-interaction --no-progress
if: "matrix.php >= 8"
- name: Unit Tests
run: php bin/pest --colors=always --exclude-group=integration
run: php bin/pest --colors=always --exclude-group=integration ${{ matrix.parallel }}
- name: Integration Tests
run: php bin/pest --colors=always --group=integration

View File

@ -4,6 +4,59 @@ 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.22.1 (2022-08-29)](https://github.com/pestphp/pest/compare/v1.22.0...v1.22.1)
### Fixed
- Initial PHP 8.2 support ([d050ef8](https://github.com/pestphp/pest/commit/d050ef8d54ffe8dfe015adeb1188aae2cca33c33))
## [v1.22.0 (2022-08-28)](https://github.com/pestphp/pest/compare/v1.21.3...v1.22.0)
### Added
- Initial PHP 8.2 support ([#563](https://github.com/pestphp/pest/pull/563))
## [v1.21.3 (2022-05-12)](https://github.com/pestphp/pest/compare/v1.21.2...v1.21.3)
### Fixed
- Debug of high order tests ([c1b32b9](https://github.com/pestphp/pest/commit/c1b32b9ffb5134803c490592454b11b8c05ea27d))
## [v1.21.2 (2022-03-05)](https://github.com/pestphp/pest/compare/v1.21.1...v1.21.2)
### Fixed
- `toThrow` expectation when exception does not exist ([#487](https://github.com/pestphp/pest/pull/487))
## [v1.21.1 (2021-11-25)](https://github.com/pestphp/pest/compare/v1.21.0...v1.21.1)
### Fixed
- sequence callables causing problems ([#442](https://github.com/pestphp/pest/pull/442))
## [v1.21.0 (2021-11-17)](https://github.com/pestphp/pest/compare/v1.20.0...v1.21.0)
### Added
- warn about xdebug modes ([1e011c](https://github.com/pestphp/pest/commit/1e011c7b4074d08f5dabab1f927d45383c85d210))
## [v1.20.0 (2021-09-25)](https://github.com/pestphp/pest/compare/v1.19.0...v1.20.0)
### Added
- `throwsIf` test call ([#371](https://github.com/pestphp/pest/pull/371))
- `--ci` CLI option to ignore development options like `->local()` ([#405](https://github.com/pestphp/pest/pull/405))
- `when` conditional expectation ([#406](https://github.com/pestphp/pest/pull/406))
- `unless` conditional expectation ([b43a598](https://github.com/pestphp/pest/commit/b43a59868d5b790a28cbb29c6110c9f068b0b812))
- `match` conditional expectation ([#407](https://github.com/pestphp/pest/pull/407))
### Fixed
- `sequence` with more expectations than iterable elements ([#399](https://github.com/pestphp/pest/pull/399))
## [v1.19.0 (2021-09-20)](https://github.com/pestphp/pest/compare/v1.18.0...v1.19.0)
### Added
- PHP 8.1 support ([e6c7d68](https://github.com/pestphp/pest/commit/e6c7d68defaec8efe01e71e15dd8d8c45b0cf60f))
- `toHaveProperties` expectation ([#391](https://github.com/pestphp/pest/pull/391))
## [v1.18.0 (2021-08-30)](https://github.com/pestphp/pest/compare/v1.17.0...v1.18.0)
### Added
- `toHaveLength` expectation ([#386](https://github.com/pestphp/pest/pull/386))
- `nunomaduro/collision:^6.0` support ([4ae482c](https://github.com/pestphp/pest/commit/4ae482c7073fb77782b8a4b5738ef1fcea0f82ab))
## [v1.17.0 (2021-08-26)](https://github.com/pestphp/pest/compare/v1.16.0...v1.17.0)
### Added
- `toThrow` expectation ([#361](https://github.com/pestphp/pest/pull/361))
## [v1.16.0 (2021-08-19)](https://github.com/pestphp/pest/compare/v1.15.0...v1.16.0)
### Added
- Support for new parallel options ([#369](https://github.com/pestphp/pest/pull/369))
## [v1.15.0 (2021-08-04)](https://github.com/pestphp/pest/compare/v1.14.0...v1.15.0)
### Added
- `toBeTruthy` and `toBeFalsy` ([#367](https://github.com/pestphp/pest/pull/367))

View File

@ -31,6 +31,10 @@ composer lint
```
## Tests
Update the snapshots:
```bash
composer update:snapshots
```
Run all tests:
```bash
composer test

View File

@ -19,12 +19,18 @@
We would like to extend our thanks to the following sponsors for funding Pest development. If you are interested in becoming a sponsor, please visit the Nuno Maduro's [Sponsors page](https://github.com/sponsors/nunomaduro).
### Platinum Sponsors
- **[Spatie](https://spatie.be)**
- **[Worksome](https://www.worksome.com/)**
### Premium Sponsors
- **[Akaunting](https://akaunting.com)**
- **[Codecourse](https://codecourse.com/)**
- **[Meema](https://meema.io/)**
- **[Scout APM](https://scoutapm.com)**
- **[Spatie](https://spatie.be/)**
- [Akaunting](https://akaunting.com)
- [Auth0](https://auth0.com)
- [Codecourse](https://codecourse.com/)
- [Fathom Analytics](https://usefathom.com/)
- [Meema](https://meema.io)
- [Scout APM](https://scoutapm.com)
Pest is an open-sourced software licensed under the **[MIT license](https://opensource.org/licenses/MIT)**.

View File

@ -2,14 +2,15 @@
When releasing a new version of Pest there are some checks and updates that need to be done:
- Clear your local repository with: `git add . && git reset --hard && git checkout master`
- On the GitHub repository, check the contents of [github.com/pestphp/pest/compare/{latest_version}...master](https://github.com/pestphp/pest/compare/{latest_version}...master) and update the [changelog](CHANGELOG.md) file with the main changes for this release
- Clear your local repository with: `git add . && git reset --hard && git checkout 1.x`
- On the GitHub repository, check the contents of [github.com/pestphp/pest/compare/{latest_version}...1.x](https://github.com/pestphp/pest/compare/{latest_version}...1.x) and update the [changelog](CHANGELOG.md) file with the main changes for this release
- Update the version number in [src/Pest.php](src/Pest.php)
- Run the tests locally using: `composer test`
- Commit the CHANGELOG and Pest file with the message: `git commit -m "release: vX.X.X"`
- Push the changes to GitHub
- Check that the CI is passing as expected: [github.com/pestphp/pest/actions](https://github.com/pestphp/pest/actions)
- Tag and push the tag with `git tag vX.X.X && git push --tags`
- Publish release here: [github.com/pestphp/pest/releases/new](https://github.com/pestphp/pest/releases/new).
### Plugins

View File

@ -3,9 +3,9 @@
use NunoMaduro\Collision\Provider;
use Pest\Actions\ValidatesEnvironment;
use Pest\Console\Command;
use Pest\Support\Container;
use Pest\TestSuite;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Console\Output\OutputInterface;
@ -42,14 +42,22 @@ use Symfony\Component\Console\Output\OutputInterface;
ValidatesEnvironment::in($testSuite);
$args = $_SERVER['argv'];
// Let's remove any arguments that PHPUnit does not understand
if ($argv->hasParameterOption('--test-directory')) {
foreach ($_SERVER['argv'] as $key => $value) {
foreach ($args as $key => $value) {
if (strpos($value, '--test-directory') !== false) {
unset($_SERVER['argv'][$key]);
unset($args[$key]);
}
}
}
exit($container->get(Command::class)->run($_SERVER['argv']));
if (($runInParallel = $argv->hasParameterOption(['--parallel', '-p'])) && !class_exists(\Pest\Parallel\Command::class)) {
$output->writeln("Parallel support requires the Pest Parallel plugin. Run <fg=yellow;options=bold>`composer require --dev pestphp/pest-plugin-parallel`</> first.");
exit(Command::FAILURE);
}
$command = $runInParallel ? \Pest\Parallel\Command::class : \Pest\Console\Command::class;
exit($container->get($command)->run($args));
})();

View File

@ -18,7 +18,7 @@
],
"require": {
"php": "^7.3 || ^8.0",
"nunomaduro/collision": "^5.4.0",
"nunomaduro/collision": "^5.10.0|^6.0",
"pestphp/pest-plugin": "^1.0.0",
"phpunit/phpunit": "^9.5.5"
},
@ -43,40 +43,47 @@
"illuminate/console": "^8.47.0",
"illuminate/support": "^8.47.0",
"laravel/dusk": "^6.15.0",
"pestphp/pest-dev-tools": "dev-master"
"pestphp/pest-dev-tools": "dev-master",
"pestphp/pest-plugin-parallel": "^1.0"
},
"minimum-stability": "dev",
"prefer-stable": true,
"config": {
"sort-packages": true,
"preferred-install": "dist"
"preferred-install": "dist",
"allow-plugins": {
"pestphp/pest-plugin": true
}
},
"bin": [
"bin/pest"
],
"scripts": {
"lint": "php-cs-fixer fix -v",
"test:lint": "php-cs-fixer fix -v --dry-run",
"lint": "PHP_CS_FIXER_IGNORE_ENV=true php-cs-fixer fix -v",
"test:lint": "PHP_CS_FIXER_IGNORE_ENV=true php-cs-fixer fix -v --dry-run",
"test:types": "phpstan analyse --ansi --memory-limit=-1",
"test:unit": "php bin/pest --colors=always --exclude-group=integration",
"test:parallel": "php bin/pest -p --colors=always --exclude-group=integration",
"test:integration": "php bin/pest --colors=always --group=integration",
"update:snapshots": "REBUILD_SNAPSHOTS=true php bin/pest --colors=always",
"test": [
"@test:lint",
"@test:types",
"@test:unit",
"@test:parallel",
"@test:integration"
]
},
"extra": {
"branch-alias": {
"dev-master": "1.x-dev"
"dev-1.x": "1.x-dev"
},
"pest": {
"plugins": [
"Pest\\Plugins\\Coverage",
"Pest\\Plugins\\Init",
"Pest\\Plugins\\Version"
"Pest\\Plugins\\Version",
"Pest\\Plugins\\Environment"
]
},
"laravel": {

287
phpstan-baseline.neon Normal file
View File

@ -0,0 +1,287 @@
parameters:
ignoreErrors:
-
message: "#^Cannot access an offset on mixed\\.$#"
count: 1
path: src/Actions/AddsDefaults.php
-
message: "#^Parameter \\#1 \\$out of class Pest\\\\Logging\\\\JUnit constructor expects string, mixed given\\.$#"
count: 1
path: src/Actions/AddsDefaults.php
-
message: "#^Parameter \\#2 \\$verbose of class NunoMaduro\\\\Collision\\\\Adapters\\\\Phpunit\\\\Printer constructor expects bool, mixed given\\.$#"
count: 1
path: src/Actions/AddsDefaults.php
-
message: "#^Parameter \\#2 \\$verbose of class Pest\\\\Logging\\\\TeamCity constructor expects bool, mixed given\\.$#"
count: 1
path: src/Actions/AddsDefaults.php
-
message: "#^Parameter \\#3 \\$colors of class NunoMaduro\\\\Collision\\\\Adapters\\\\Phpunit\\\\Printer constructor expects string, mixed given\\.$#"
count: 1
path: src/Actions/AddsDefaults.php
-
message: "#^Parameter \\#3 \\$colors of class Pest\\\\Logging\\\\TeamCity constructor expects string, mixed given\\.$#"
count: 1
path: src/Actions/AddsDefaults.php
-
message: "#^Parameter \\#1 \\$filename of function file_exists expects string, mixed given\\.$#"
count: 1
path: src/Actions/ValidatesConfiguration.php
-
message: "#^Parameter \\#1 \\$filename of method PHPUnit\\\\TextUI\\\\XmlConfiguration\\\\Loader\\:\\:load\\(\\) expects string, mixed given\\.$#"
count: 1
path: src/Actions/ValidatesConfiguration.php
-
message: "#^Parameter \\#1 \\$loader of class PHPUnit\\\\TextUI\\\\TestRunner constructor expects PHPUnit\\\\Runner\\\\TestSuiteLoader\\|null, mixed given\\.$#"
count: 1
path: src/Console/Command.php
-
message: "#^Parameter \\#1 \\$testSuite of static method Pest\\\\Actions\\\\AddsTests\\:\\:to\\(\\) expects iterable\\<PHPUnit\\\\Framework\\\\TestCase\\>&PHPUnit\\\\Framework\\\\TestSuite, mixed given\\.$#"
count: 1
path: src/Console/Command.php
-
message: "#^Parameter \\#2 \\$suffixes of method SebastianBergmann\\\\FileIterator\\\\Facade\\:\\:getFilesAsArray\\(\\) expects array\\|string, mixed given\\.$#"
count: 1
path: src/Console/Command.php
-
message: "#^Argument of an invalid type mixed supplied for foreach, only iterables are supported\\.$#"
count: 1
path: src/Datasets.php
-
message: "#^Method Pest\\\\Datasets\\:\\:getDataSetsCombinations\\(\\) has parameter \\$combinations with no value type specified in iterable type array\\.$#"
count: 1
path: src/Datasets.php
-
message: "#^Method Pest\\\\Datasets\\:\\:getDataSetsCombinations\\(\\) return type has no value type specified in iterable type array\\.$#"
count: 1
path: src/Datasets.php
-
message: "#^Method Pest\\\\Datasets\\:\\:processDatasets\\(\\) return type has no value type specified in iterable type array\\.$#"
count: 1
path: src/Datasets.php
-
message: "#^Parameter \\#1 \\$key of static method Pest\\\\Datasets\\:\\:getDataSetDescription\\(\\) expects int\\|string, mixed given\\.$#"
count: 1
path: src/Datasets.php
-
message: "#^Argument of an invalid type mixed supplied for foreach, only iterables are supported\\.$#"
count: 1
path: src/Each.php
-
message: "#^Parameter \\#1 \\$ of callable callable\\(Pest\\\\Expectation\\<TValue\\>\\)\\: mixed expects Pest\\\\Expectation\\<TValue\\>, Pest\\\\Expectation\\<mixed\\> given\\.$#"
count: 1
path: src/Expectation.php
-
message: "#^Parameter \\#1 \\$actualJson of static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertJson\\(\\) expects string, mixed given\\.$#"
count: 1
path: src/Expectation.php
-
message: "#^Parameter \\#1 \\$directory of static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertDirectoryExists\\(\\) expects string, mixed given\\.$#"
count: 1
path: src/Expectation.php
-
message: "#^Parameter \\#1 \\$directory of static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertDirectoryIsReadable\\(\\) expects string, mixed given\\.$#"
count: 1
path: src/Expectation.php
-
message: "#^Parameter \\#1 \\$directory of static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertDirectoryIsWritable\\(\\) expects string, mixed given\\.$#"
count: 1
path: src/Expectation.php
-
message: "#^Parameter \\#1 \\$file of static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertFileIsReadable\\(\\) expects string, mixed given\\.$#"
count: 1
path: src/Expectation.php
-
message: "#^Parameter \\#1 \\$file of static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertFileIsWritable\\(\\) expects string, mixed given\\.$#"
count: 1
path: src/Expectation.php
-
message: "#^Parameter \\#1 \\$filename of static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertFileExists\\(\\) expects string, mixed given\\.$#"
count: 1
path: src/Expectation.php
-
message: "#^Parameter \\#1 \\$json of function json_decode expects string, mixed given\\.$#"
count: 1
path: src/Expectation.php
-
message: "#^Parameter \\#1 \\$needle of static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertStringContainsString\\(\\) expects string, mixed given\\.$#"
count: 1
path: src/Expectation.php
-
message: "#^Parameter \\#1 \\$object_or_class of function property_exists expects object\\|string, mixed given\\.$#"
count: 2
path: src/Expectation.php
-
message: "#^Parameter \\#2 \\$haystack of static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertContains\\(\\) expects iterable, mixed given\\.$#"
count: 1
path: src/Expectation.php
-
message: "#^Parameter \\#2 \\$haystack of static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertCount\\(\\) expects Countable\\|iterable, mixed given\\.$#"
count: 1
path: src/Expectation.php
-
message: "#^Parameter \\#2 \\$string of static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertMatchesRegularExpression\\(\\) expects string, mixed given\\.$#"
count: 1
path: src/Expectation.php
-
message: "#^Parameter \\#2 \\$string of static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertStringEndsWith\\(\\) expects string, mixed given\\.$#"
count: 1
path: src/Expectation.php
-
message: "#^Parameter \\#2 \\$string of static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertStringStartsWith\\(\\) expects string, mixed given\\.$#"
count: 1
path: src/Expectation.php
-
message: "#^Parameter \\#2 \\$value of method Pest\\\\Expectation\\<TValue\\>\\:\\:retrieve\\(\\) expects array\\<string, null\\>\\|object, mixed given\\.$#"
count: 1
path: src/Expectation.php
-
message: "#^Trying to invoke mixed but it's not a callable\\.$#"
count: 1
path: src/Expectation.php
-
message: "#^Method Pest\\\\Factories\\\\TestCaseFactory\\:\\:build\\(\\) should return array\\<int, PHPUnit\\\\Framework\\\\TestCase\\> but returns array\\<int, object\\>\\.$#"
count: 1
path: src/Factories/TestCaseFactory.php
-
message: "#^Function it\\(\\) should return Pest\\\\PendingObjects\\\\TestCall but returns mixed\\.$#"
count: 1
path: src/Functions.php
-
message: "#^Parameter \\#2 \\$classAndTraits of class Pest\\\\PendingObjects\\\\UsesCall constructor expects array\\<int, string\\>, array\\<int\\|string, string\\> given\\.$#"
count: 1
path: src/Functions.php
-
message: "#^Parameter \\#2 \\$value of method Pest\\\\HigherOrderExpectation\\:\\:retrieve\\(\\) expects array\\<string, null\\>\\|object, mixed given\\.$#"
count: 1
path: src/HigherOrderExpectation.php
-
message: "#^Dead catch \\- ReflectionException is never thrown in the try block\\.$#"
count: 1
path: src/Logging/JUnit.php
-
message: "#^Parameter \\#2 \\$value of method DOMElement\\:\\:setAttribute\\(\\) expects string, mixed given\\.$#"
count: 1
path: src/Logging/JUnit.php
-
message: "#^Parameter \\$test of method Pest\\\\Logging\\\\JUnit\\:\\:startTest\\(\\) has invalid type Pest\\\\Concerns\\\\Testable\\.$#"
count: 1
path: src/Logging/JUnit.php
-
message: "#^Parameter \\$test of method Pest\\\\Logging\\\\TeamCity\\:\\:endTest\\(\\) has invalid type Pest\\\\Concerns\\\\Testable\\.$#"
count: 1
path: src/Logging/TeamCity.php
-
message: "#^Parameter \\$test of method Pest\\\\Logging\\\\TeamCity\\:\\:startTest\\(\\) has invalid type Pest\\\\Concerns\\\\Testable\\.$#"
count: 1
path: src/Logging/TeamCity.php
-
message: "#^Dead catch \\- PHPUnit\\\\Framework\\\\ExpectationFailedException is never thrown in the try block\\.$#"
count: 1
path: src/OppositeExpectation.php
-
message: "#^Property Pest\\\\PendingObjects\\\\UsesCall\\:\\:\\$groups \\(array\\<int, string\\>\\) does not accept array\\<int\\|string, string\\>\\.$#"
count: 1
path: src/PendingObjects/UsesCall.php
-
message: "#^Cannot cast mixed to float\\.$#"
count: 1
path: src/Plugins/Coverage.php
-
message: "#^Static property Pest\\\\Plugins\\\\Environment\\:\\:\\$instance is unused\\.$#"
count: 1
path: src/Plugins/Environment.php
-
message: "#^Method Pest\\\\Support\\\\Container\\:\\:get\\(\\) should return object but returns mixed\\.$#"
count: 1
path: src/Support/Container.php
-
message: "#^Argument of an invalid type mixed supplied for foreach, only iterables are supported\\.$#"
count: 1
path: src/Support/ExceptionTrace.php
-
message: "#^Cannot access offset 'file' on mixed\\.$#"
count: 1
path: src/Support/ExceptionTrace.php
-
message: "#^Parameter \\#1 \\$haystack of function mb_strpos expects string, mixed given\\.$#"
count: 1
path: src/Support/ExceptionTrace.php
-
message: "#^Parameter \\#2 \\$array of function key_exists expects array, mixed given\\.$#"
count: 1
path: src/Support/ExceptionTrace.php
-
message: "#^Method Pest\\\\Support\\\\HigherOrderCallables\\:\\:expect\\(\\) should return Pest\\\\Expectation\\<TValue\\> but returns Pest\\\\Expectation\\<mixed\\>\\.$#"
count: 1
path: src/Support/HigherOrderCallables.php
-
message: "#^Parameter \\#1 \\$target of method Pest\\\\Support\\\\HigherOrderMessage\\:\\:call\\(\\) expects object, mixed given\\.$#"
count: 1
path: src/Support/HigherOrderMessageCollection.php
-
message: "#^Constant Pest\\\\Support\\\\HigherOrderTapProxy\\:\\:UNDEFINED_PROPERTY is unused\\.$#"
count: 1
path: src/Support/HigherOrderTapProxy.php
-
message: "#^Dead catch \\- Throwable is never thrown in the try block\\.$#"
count: 1
path: src/Support/HigherOrderTapProxy.php

View File

@ -2,6 +2,7 @@ includes:
- vendor/phpstan/phpstan-strict-rules/rules.neon
- vendor/ergebnis/phpstan-rules/rules.neon
- vendor/thecodingmachine/phpstan-strict-rules/phpstan-strict-rules.neon
- phpstan-baseline.neon
parameters:
level: max
@ -23,9 +24,6 @@ parameters:
-
message: '#Call to an undefined method PHPUnit\\Framework\\Test::getName\(\)#'
path: src/Logging
-
message: '#invalid typehint type Pest\\Concerns\\Testable#'
path: src/Logging
-
message: '#is not subtype of native type PHPUnit\\Framework\\Test#'
path: src/Logging

View File

@ -0,0 +1,50 @@
<?php
declare(strict_types=1);
namespace Pest\Actions;
use Pest\Contracts\Plugins\AddsOutput;
use Pest\Contracts\Plugins\HandlesArguments;
use Pest\Plugin\Loader;
/**
* @internal
*/
final class InteractsWithPlugins
{
/**
* Transform the input arguments by passing it to the relevant plugins.
*
* @param array<int, string> $argv
*
* @return array<int, string>
*/
public static function handleArguments(array $argv): array
{
$plugins = Loader::getPlugins(HandlesArguments::class);
/** @var HandlesArguments $plugin */
foreach ($plugins as $plugin) {
$argv = $plugin->handleArguments($argv);
}
return $argv;
}
/**
* Provides an opportunity for any plugins that want
* to provide additional output after test execution.
*/
public static function addOutput(int $result): int
{
$plugins = Loader::getPlugins(AddsOutput::class);
/** @var AddsOutput $plugin */
foreach ($plugins as $plugin) {
$result = $plugin->addOutput($result);
}
return $result;
}
}

View File

@ -5,7 +5,9 @@ declare(strict_types=1);
namespace Pest\Actions;
use Pest\Support\Str;
use function Pest\testDirectory;
use PHPUnit\Util\FileLoader;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
@ -50,6 +52,7 @@ final class LoadStructure
$directory = new RecursiveDirectoryIterator($filename);
$iterator = new RecursiveIteratorIterator($directory);
foreach ($iterator as $file) {
/* @phpstan-ignore-next-line */
$filename = $file->__toString();
if (Str::endsWith($filename, '.php') && file_exists($filename)) {
require_once $filename;

View File

@ -6,11 +6,9 @@ namespace Pest\Console;
use Pest\Actions\AddsDefaults;
use Pest\Actions\AddsTests;
use Pest\Actions\InteractsWithPlugins;
use Pest\Actions\LoadStructure;
use Pest\Actions\ValidatesConfiguration;
use Pest\Contracts\Plugins\AddsOutput;
use Pest\Contracts\Plugins\HandlesArguments;
use Pest\Plugin\Loader;
use Pest\Plugins\Version;
use Pest\Support\Container;
use Pest\TestSuite;
@ -57,23 +55,12 @@ final class Command extends BaseCommand
*/
protected function handleArguments(array $argv): void
{
/*
* First, let's call all plugins that want to handle arguments
*/
$plugins = Loader::getPlugins(HandlesArguments::class);
$argv = InteractsWithPlugins::handleArguments($argv);
/** @var HandlesArguments $plugin */
foreach ($plugins as $plugin) {
$argv = $plugin->handleArguments($argv);
}
/*
* Next, as usual, let's send the console arguments to PHPUnit.
*/
parent::handleArguments($argv);
/*
* Finally, let's validate the configuration. Making
* Let's validate the configuration. Making
* sure all options are yet supported by Pest.
*/
ValidatesConfiguration::in($this->arguments);
@ -128,16 +115,7 @@ final class Command extends BaseCommand
LoadStructure::in($this->testSuite->rootPath);
$result = parent::run($argv, false);
/*
* Let's call all plugins that want to add output after test execution
*/
$plugins = Loader::getPlugins(AddsOutput::class);
/** @var AddsOutput $plugin */
foreach ($plugins as $plugin) {
$result = $plugin->addOutput($result);
}
$result = InteractsWithPlugins::addOutput($result);
exit($result);
}

View File

@ -12,9 +12,6 @@ interface HandlesArguments
/**
* Allows to handle custom command line arguments.
*
* PLEASE NOTE: it is necessary to remove any custom argument from the array
* because otherwise the application will complain about them
*
* @param array<int, string> $arguments
*
* @return array<int, string> the updated list of arguments

View File

@ -5,13 +5,20 @@ declare(strict_types=1);
namespace Pest;
use BadMethodCallException;
use Closure;
use Error;
use InvalidArgumentException;
use Pest\Concerns\Extendable;
use Pest\Concerns\RetrievesValues;
use Pest\Support\Arr;
use Pest\Support\NullClosure;
use PHPUnit\Framework\Assert;
use PHPUnit\Framework\Constraint\Constraint;
use PHPUnit\Framework\ExpectationFailedException;
use ReflectionFunction;
use ReflectionNamedType;
use SebastianBergmann\Exporter\Exporter;
use Throwable;
/**
* @internal
@ -102,7 +109,6 @@ final class Expectation
public function ray(...$arguments): self
{
if (function_exists('ray')) {
// @phpstan-ignore-next-line
ray($this->value, ...$arguments);
}
@ -148,9 +154,10 @@ final class Expectation
throw new BadMethodCallException('Expectation value is not iterable.');
}
$value = is_array($this->value) ? $this->value : iterator_to_array($this->value);
$keys = array_keys($value);
$values = array_values($value);
$value = is_array($this->value) ? $this->value : iterator_to_array($this->value);
$keys = array_keys($value);
$values = array_values($value);
$callbacksCount = count($callbacks);
$index = 0;
@ -159,8 +166,12 @@ final class Expectation
$index = $index < count($values) - 1 ? $index + 1 : 0;
}
if ($callbacksCount > count($values)) {
Assert::assertLessThanOrEqual(count($value), count($callbacks));
}
foreach ($values as $key => $item) {
if (is_callable($callbacks[$key])) {
if ($callbacks[$key] instanceof Closure) {
call_user_func($callbacks[$key], new self($item), new self($keys[$key]));
continue;
}
@ -171,6 +182,88 @@ final class Expectation
return $this;
}
/**
* If the subject matches one of the given "expressions", the expression callback will run.
*
* @template TMatchSubject of array-key
*
* @param callable(): TMatchSubject|TMatchSubject $subject
* @param array<TMatchSubject, (callable(Expectation<TValue>): mixed)|TValue> $expressions
*/
public function match($subject, array $expressions): Expectation
{
$subject = is_callable($subject)
? $subject
: function () use ($subject) {
return $subject;
};
$subject = $subject();
$matched = false;
foreach ($expressions as $key => $callback) {
if ($subject != $key) {
continue;
}
$matched = true;
if (is_callable($callback)) {
$callback(new self($this->value));
continue;
}
$this->and($this->value)->toEqual($callback);
break;
}
if ($matched === false) {
throw new ExpectationFailedException('Unhandled match value.');
}
return $this;
}
/**
* Apply the callback if the given "condition" is falsy.
*
* @param (callable(): bool)|bool $condition
* @param callable(Expectation<TValue>): mixed $callback
*/
public function unless($condition, callable $callback): Expectation
{
$condition = is_callable($condition)
? $condition
: static function () use ($condition): bool {
return (bool) $condition; // @phpstan-ignore-line
};
return $this->when(!$condition(), $callback);
}
/**
* Apply the callback if the given "condition" is truthy.
*
* @param (callable(): bool)|bool $condition
* @param callable(Expectation<TValue>): mixed $callback
*/
public function when($condition, callable $callback): Expectation
{
$condition = is_callable($condition)
? $condition
: static function () use ($condition): bool {
return (bool) $condition; // @phpstan-ignore-line
};
if ($condition()) {
$callback($this->and($this->value));
}
return $this;
}
/**
* Asserts that two variables have the same type and
* value. Used on objects, it asserts that two
@ -321,6 +414,36 @@ final class Expectation
return $this;
}
/**
* Asserts that $number matches value's Length.
*/
public function toHaveLength(int $number): Expectation
{
if (is_string($this->value)) {
Assert::assertEquals($number, mb_strlen($this->value));
return $this;
}
if (is_iterable($this->value)) {
return $this->toHaveCount($number);
}
if (is_object($this->value)) {
if (method_exists($this->value, 'toArray')) {
$array = $this->value->toArray();
} else {
$array = (array) $this->value;
}
Assert::assertCount($number, $array);
return $this;
}
throw new BadMethodCallException('Expectation value length is not countable.');
}
/**
* Asserts that $count matches the number of elements of the value.
*/
@ -350,6 +473,20 @@ final class Expectation
return $this;
}
/**
* Asserts that the value contains the provided properties $names.
*
* @param iterable<array-key, string> $names
*/
public function toHaveProperties(iterable $names): Expectation
{
foreach ($names as $name) {
$this->toHaveProperty($name);
}
return $this;
}
/**
* Asserts that two variables have the same value.
*
@ -586,7 +723,7 @@ final class Expectation
try {
Assert::assertTrue(Arr::has($array, $key));
/* @phpstan-ignore-next-line */
/* @phpstan-ignore-next-line */
} catch (ExpectationFailedException $exception) {
throw new ExpectationFailedException("Failed asserting that an array has the key '$key'", $exception->getComparisonFailure());
}
@ -749,6 +886,60 @@ final class Expectation
return $this;
}
/**
* Asserts that executing value throws an exception.
*
* @param (Closure(Throwable): mixed)|string $exception
*/
public function toThrow($exception, string $exceptionMessage = null): Expectation
{
$callback = NullClosure::create();
if ($exception instanceof Closure) {
$callback = $exception;
$parameters = (new ReflectionFunction($exception))->getParameters();
if (1 !== count($parameters)) {
throw new InvalidArgumentException('The given closure must have a single parameter type-hinted as the class string.');
}
if (!($type = $parameters[0]->getType()) instanceof ReflectionNamedType) {
throw new InvalidArgumentException('The given closure\'s parameter must be type-hinted as the class string.');
}
$exception = $type->getName();
}
try {
($this->value)();
} catch (Throwable $e) {
if (!class_exists($exception)) {
if ($e instanceof Error && (bool) preg_match("/Class [\"']{$exception}[\"'] not found/", $e->getMessage())) {
throw $e;
}
Assert::assertStringContainsString($exception, $e->getMessage());
return $this;
}
if ($exceptionMessage !== null) {
Assert::assertStringContainsString($exceptionMessage, $e->getMessage());
}
Assert::assertInstanceOf($exception, $e);
$callback($e);
return $this;
}
if (!class_exists($exception)) {
throw new ExpectationFailedException("Exception with message \"{$exception}\" not thrown.");
}
throw new ExpectationFailedException("Exception \"{$exception}\" not thrown.");
}
/**
* Exports the given value.
*

View File

@ -216,6 +216,7 @@ final class TestCaseFactory
eval("
namespace $namespace;
#[\AllowDynamicProperties]
final class $className extends $baseClass implements $hasPrintableTestCaseClassFQN {
$traitsCode

View File

@ -8,7 +8,9 @@ use Illuminate\Console\Command;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Str;
use Pest\Exceptions\InvalidConsoleArgument;
use function Pest\testDirectory;
use Pest\TestSuite;
/**

View File

@ -8,7 +8,9 @@ use Illuminate\Console\Command;
use Illuminate\Support\Facades\File;
use Pest\Console\Thanks;
use Pest\Exceptions\InvalidConsoleArgument;
use function Pest\testDirectory;
use Pest\TestSuite;
/**

View File

@ -8,7 +8,9 @@ use Illuminate\Console\Command;
use Illuminate\Support\Facades\File;
use Pest\Exceptions\InvalidConsoleArgument;
use Pest\Support\Str;
use function Pest\testDirectory;
use Pest\TestSuite;
/**

View File

@ -13,11 +13,14 @@ declare(strict_types=1);
namespace Pest\Logging;
use function class_exists;
use DOMDocument;
use DOMElement;
use Exception;
use function get_class;
use function method_exists;
use Pest\Concerns\Testable;
use PHPUnit\Framework\AssertionFailedError;
use PHPUnit\Framework\ExceptionWrapper;
@ -32,9 +35,12 @@ use PHPUnit\Util\Printer;
use PHPUnit\Util\Xml;
use ReflectionClass;
use ReflectionException;
use function sprintf;
use function str_replace;
use Throwable;
use function trim;
/**
@ -173,7 +179,6 @@ final class JUnit extends Printer implements TestListener
$this->doAddSkipped();
}
/** @phpstan-ignore-next-line */
public function startTestSuite(TestSuite $suite): void
{
$testSuite = $this->document->createElement('testsuite');
@ -212,7 +217,6 @@ final class JUnit extends Printer implements TestListener
$this->testSuiteTimes[$this->testSuiteLevel] = 0;
}
/** @phpstan-ignore-next-line */
public function endTestSuite(TestSuite $suite): void
{
$this->testSuites[$this->testSuiteLevel]->setAttribute(
@ -283,7 +287,6 @@ final class JUnit extends Printer implements TestListener
$class = new ReflectionClass($test);
// @codeCoverageIgnoreStart
} catch (ReflectionException $e) {
// @phpstan-ignore-next-line
throw new Exception($e->getMessage(), (int) $e->getCode(), $e);
}
// @codeCoverageIgnoreEnd

View File

@ -5,10 +5,13 @@ declare(strict_types=1);
namespace Pest\Logging;
use function getmypid;
use Pest\Concerns\Logging\WritesToConsole;
use Pest\Concerns\Testable;
use Pest\Support\ExceptionTrace;
use function Pest\version;
use PHPUnit\Framework\AssertionFailedError;
use PHPUnit\Framework\Test;
use PHPUnit\Framework\TestCase;
@ -16,9 +19,11 @@ use PHPUnit\Framework\TestResult;
use PHPUnit\Framework\TestSuite;
use PHPUnit\Framework\Warning;
use PHPUnit\TextUI\DefaultResultPrinter;
use function round;
use function str_replace;
use function strlen;
use Throwable;
final class TeamCity extends DefaultResultPrinter
@ -106,7 +111,6 @@ final class TeamCity extends DefaultResultPrinter
- $result->riskyCount();
}
/** @phpstan-ignore-next-line */
public function startTestSuite(TestSuite $suite): void
{
$suiteName = $suite->getName();
@ -164,7 +168,6 @@ final class TeamCity extends DefaultResultPrinter
);
}
/** @phpstan-ignore-next-line */
public function endTestSuite(TestSuite $suite): void
{
$suiteName = $suite->getName();
@ -286,7 +289,7 @@ final class TeamCity extends DefaultResultPrinter
{
$this->markAsFailure($t);
$this->writeWarning($test->getName());
$this->phpunitTeamCity->addSkippedTest($test, $t, $time);
$this->phpunitTeamCity->printIgnoredTest($test->getName(), $t, $time);
}
private function markAsFailure(Throwable $t): void

View File

@ -51,6 +51,8 @@ final class OppositeExpectation
* Handle dynamic method calls into the original expectation.
*
* @param array<int, mixed> $arguments
*
* @return Expectation|never
*/
public function __call(string $name, array $arguments): Expectation
{
@ -61,12 +63,13 @@ final class OppositeExpectation
return $this->original;
}
// @phpstan-ignore-next-line
$this->throwExpectationFailedException($name, $arguments);
}
/**
* Handle dynamic properties gets into the original expectation.
*
* @return Expectation|never
*/
public function __get(string $name): Expectation
{
@ -77,7 +80,6 @@ final class OppositeExpectation
return $this->original;
}
// @phpstan-ignore-next-line
$this->throwExpectationFailedException($name);
}
@ -85,6 +87,8 @@ final class OppositeExpectation
* Creates a new expectation failed exception with a nice readable message.
*
* @param array<int, mixed> $arguments
*
* @return never
*/
private function throwExpectationFailedException(string $name, array $arguments = []): void
{

View File

@ -78,6 +78,26 @@ final class TestCall
return $this;
}
/**
* Asserts that the test throws the given `$exceptionClass` when called if the given condition is true.
*
* @param (callable(): bool)|bool $condition
*/
public function throwsIf($condition, string $exception, string $exceptionMessage = null): TestCall
{
$condition = is_callable($condition)
? $condition
: static function () use ($condition): bool {
return (bool) $condition; // @phpstan-ignore-line
};
if ($condition()) {
return $this->throws($exception, $exceptionMessage);
}
return $this;
}
/**
* Runs the current test multiple times with
* each item of the given `iterable`.
@ -140,7 +160,7 @@ final class TestCall
$condition = is_callable($condition)
? $condition
: function () use ($condition) { /* @phpstan-ignore-line */
: function () use ($condition) {
return $condition;
};
@ -163,7 +183,7 @@ final class TestCall
*/
public function __get(string $name): self
{
return $this->addChain($name);
return $this->addChain(Backtrace::file(), Backtrace::line(), $name);
}
/**
@ -173,7 +193,7 @@ final class TestCall
*/
public function __call(string $name, array $arguments): self
{
return $this->addChain($name, $arguments);
return $this->addChain(Backtrace::file(), Backtrace::line(), $name, $arguments);
}
/**
@ -181,11 +201,11 @@ final class TestCall
*
* @param array<int, mixed>|null $arguments
*/
private function addChain(string $name, array $arguments = null): self
private function addChain(string $file, int $line, string $name, array $arguments = null): self
{
$this->testCaseFactory
->chains
->add(Backtrace::file(), Backtrace::line(), $name, $arguments);
->add($file, $line, $name, $arguments);
if ($this->descriptionLess) {
$exporter = new Exporter();

View File

@ -6,7 +6,7 @@ namespace Pest;
function version(): string
{
return '1.15.0';
return '1.22.1';
}
function testDirectory(string $file = ''): string

View File

@ -0,0 +1,67 @@
<?php
declare(strict_types=1);
namespace Pest\Plugins;
use Pest\Contracts\Plugins\HandlesArguments;
/**
* @internal
*/
final class Environment implements HandlesArguments
{
/**
* The continuous integration environment.
*/
public const CI = 'ci';
/**
* The local environment.
*/
public const LOCAL = 'local';
/**
* @var \Pest\Plugins\Environment|null
*/
private static $instance;
/**
* The current environment.
*
* @var string|null
*/
private static $name;
/**
* Allows to handle custom command line arguments.
*
* @param array<int, string> $arguments
*
* @return array<int, string> the updated list of arguments
*/
public function handleArguments(array $arguments): array
{
foreach ($arguments as $index => $argument) {
if ($argument === '--ci') {
unset($arguments[$index]);
self::$name = self::CI;
}
}
return array_values($arguments);
}
/**
* Gets the environment name.
*/
public static function name(string $name = null): string
{
if (is_string($name)) {
self::$name = $name;
}
return self::$name ?? self::LOCAL;
}
}

View File

@ -5,7 +5,9 @@ declare(strict_types=1);
namespace Pest\Plugins;
use Pest\Contracts\Plugins\HandlesArguments;
use function Pest\version;
use Symfony\Component\Console\Output\OutputInterface;
/**

View File

@ -11,6 +11,7 @@ use Pest\Exceptions\TestAlreadyExist;
use Pest\Exceptions\TestCaseAlreadyInUse;
use Pest\Exceptions\TestCaseClassOrTraitNotFound;
use Pest\Factories\TestCaseFactory;
use Pest\Plugins\Environment;
use Pest\Support\Reflection;
use Pest\Support\Str;
use Pest\TestSuite;
@ -22,7 +23,7 @@ use PHPUnit\Framework\TestCase;
final class TestRepository
{
/**
* @var string
* @var non-empty-string
*/
private const SEPARATOR = '>>>';
@ -44,6 +45,20 @@ final class TestRepository
return count($this->state);
}
/**
* Returns the filename of each test that should be executed in the suite.
*
* @return array<int, string>
*/
public function getFilenames(): array
{
$testsWithOnly = $this->testsUsingOnly();
return array_values(array_map(function (TestCaseFactory $factory): string {
return $factory->filename;
}, count($testsWithOnly) > 0 ? $testsWithOnly : $this->state));
}
/**
* Calls the given callable foreach test case.
*/
@ -85,9 +100,7 @@ final class TestRepository
}
}
$onlyState = array_filter($this->state, function ($testFactory): bool {
return $testFactory->only;
});
$onlyState = $this->testsUsingOnly();
$state = count($onlyState) > 0 ? $onlyState : $this->state;
@ -100,6 +113,22 @@ final class TestRepository
}
}
/**
* Return all tests that have called the only method.
*
* @return array<TestCaseFactory>
*/
private function testsUsingOnly(): array
{
if (Environment::name() === Environment::CI) {
return [];
}
return array_filter($this->state, function ($testFactory): bool {
return $testFactory->only;
});
}
/**
* Uses the given `$testCaseClass` on the given `$paths`.
*

View File

@ -26,6 +26,7 @@ final class Backtrace
$current = null;
foreach (debug_backtrace(self::BACKTRACE_OPTIONS) as $trace) {
assert(array_key_exists(self::FILE, $trace));
if (Str::endsWith($trace[self::FILE], (string) realpath('vendor/phpunit/phpunit/src/Util/FileLoader.php'))) {
break;
}
@ -45,7 +46,11 @@ final class Backtrace
*/
public static function file(): string
{
return debug_backtrace(self::BACKTRACE_OPTIONS)[1][self::FILE];
$trace = debug_backtrace(self::BACKTRACE_OPTIONS)[1];
assert(array_key_exists(self::FILE, $trace));
return $trace[self::FILE];
}
/**
@ -53,7 +58,11 @@ final class Backtrace
*/
public static function dirname(): string
{
return dirname(debug_backtrace(self::BACKTRACE_OPTIONS)[1][self::FILE]);
$trace = debug_backtrace(self::BACKTRACE_OPTIONS)[1];
assert(array_key_exists(self::FILE, $trace));
return dirname($trace[self::FILE]);
}
/**
@ -61,6 +70,10 @@ final class Backtrace
*/
public static function line(): int
{
return debug_backtrace(self::BACKTRACE_OPTIONS)[1]['line'];
$trace = debug_backtrace(self::BACKTRACE_OPTIONS)[1];
assert(array_key_exists('line', $trace));
return $trace['line'];
}
}

View File

@ -30,14 +30,21 @@ final class Coverage
}
/**
* Runs true there is any code
* coverage driver available.
* Runs true there is any code coverage driver available.
*/
public static function isAvailable(): bool
{
return (new Runtime())->canCollectCodeCoverage();
}
/**
* If the user is using Xdebug.
*/
public static function usingXdebug(): bool
{
return (new Runtime())->hasXdebug();
}
/**
* Reports the code coverage report to the
* console and returns the result in float.
@ -45,6 +52,14 @@ final class Coverage
public static function report(OutputInterface $output): float
{
if (!file_exists($reportPath = self::getPath())) {
if (self::usingXdebug()) {
$output->writeln(
" <fg=black;bg=yellow;options=bold> WARN </> Unable to get coverage using Xdebug. Did you set <href=https://xdebug.org/docs/code_coverage#mode>Xdebug's coverage mode</>?</>",
);
return 0.0;
}
throw ShouldNotHappen::fromMessage(sprintf('Coverage not found in path: %s.', $reportPath));
}

View File

@ -44,6 +44,7 @@ final class ExceptionTrace
*/
public static function removePestReferences(Throwable $t): void
{
/* @phpstan-ignore-next-line */
if (!property_exists($t, 'serializableTrace')) {
return;
}

View File

@ -91,8 +91,8 @@ final class TestSuite
$this->afterEach = new AfterEachRepository();
$this->afterAll = new AfterAllRepository();
$this->rootPath = (string) realpath($rootPath);
$this->testPath = $testPath;
$this->rootPath = (string) realpath($rootPath);
$this->testPath = $testPath;
}
/**

View File

@ -107,6 +107,11 @@
✓ it catch exceptions
✓ it catch exceptions and messages
✓ it can just define the message
✓ it not catch exceptions if given condition is false
✓ it catch exceptions if given condition is true
✓ it catch exceptions and messages if given condition is true
✓ it can just define the message if given condition is true
✓ it can just define the message if given condition is 1
PASS Tests\Features\Expect\HigherOrder\methods
✓ it can access methods
@ -158,6 +163,17 @@
✓ it properly parses json string
✓ fails with broken json string
PASS Tests\Features\Expect\matchExpectation
✓ it pass
✓ it failures
✓ it runs with truthy
✓ it runs with falsy
✓ it runs with truthy closure condition
✓ it runs with falsy closure condition
✓ it can be passed non-callable values
✓ it fails with unhandled match
✓ it can be used in higher order tests
PASS Tests\Features\Expect\not
✓ not property calls
@ -168,7 +184,7 @@
✓ an exception is thrown if the the type is not iterable
✓ allows for sequences of checks to be run on iterable data
✓ loops back to the start if it runs out of sequence items
it works if the number of items in the iterable is smaller than the number of expectations
fails if the number of iterable items is greater than the number of expectations
✓ it works with associative arrays
✓ it can be passed non-callable values
✓ it can be passed a mixture of value types
@ -417,6 +433,25 @@
PASS Tests\Features\Expect\toHaveKeys
✓ pass
✓ failures
✓ not failures
PASS Tests\Features\Expect\toHaveLength
✓ it passes with ('Fortaleza')
✓ it passes with ('Sollefteå')
✓ it passes with ('Ιεράπετρα')
✓ it passes with (stdClass Object (...))
✓ it passes with (Illuminate\Support\Collection Object (...))
✓ it passes with array
✓ it passes with *not*
✓ it properly fails with *not*
✓ it fails with (1)
✓ it fails with (1.5)
✓ it fails with (true)
✓ it fails with (null)
PASS Tests\Features\Expect\toHaveProperties
✓ pass
✓ failures
✓ not failures
PASS Tests\Features\Expect\toHaveProperty
@ -449,6 +484,39 @@
✓ failures
✓ not failures
PASS Tests\Features\Expect\toThrow
✓ passes
✓ failures 1
✓ failures 2
✓ failures 3
✓ failures 4
✓ failures 5
✓ failures 6
✓ failures 7
✓ not failures
✓ closure missing parameter
✓ closure missing type-hint
✓ it can handle a non-defined exception
✓ it can handle a class not found Error
PASS Tests\Features\Expect\unless
✓ it pass
✓ it failures
✓ it runs with truthy
✓ it skips with falsy
✓ it runs with truthy closure condition
✓ it skips with falsy closure condition
✓ it can be used in higher order tests
PASS Tests\Features\Expect\when
✓ it pass
✓ it failures
✓ it runs with truthy
✓ it skips with falsy
✓ it runs with truthy closure condition
✓ it skips with falsy closure condition
✓ it can be used in higher order tests
PASS Tests\Features\Helpers
✓ it can set/get properties on $this
✓ it throws error if property do not exist
@ -591,6 +659,10 @@
✓ it show the actual dataset of multiple non-named datasets in their description
✓ it show the correct description for mixed named and not-named datasets
PASS Tests\Unit\Plugins\Environment
✓ environment is set to CI when --ci option is used
✓ environment is set to Local when --ci option is not used
PASS Tests\Unit\Plugins\Version
✓ it outputs the version when --version is used
✓ it do not outputs version when --version is not used
@ -614,6 +686,9 @@
PASS Tests\Unit\TestSuite
✓ it does not allow to add the same test description twice
✓ it alerts users about tests with arguments but no input
✓ it can return an array of all test suite filenames
✓ it can filter the test suite filenames to those with the only method
✓ it does not filter the test suite filenames to those with the only method when working in CI pipeline
PASS Tests\Visual\Help
✓ visual snapshot of help command output
@ -647,5 +722,5 @@
✓ it is a test
✓ it uses correct parent class
Tests: 4 incompleted, 9 skipped, 419 passed
Tests: 4 incompleted, 9 skipped, 480 passed

View File

@ -8,7 +8,7 @@ use Symfony\Component\Console\Output\ConsoleOutput;
it('has plugin')->assertTrue(class_exists(CoveragePlugin::class));
it('adds coverage if --coverage exist', function () {
$plugin = new CoveragePlugin(new ConsoleOutput());
$plugin = new CoveragePlugin(new ConsoleOutput());
$testSuite = TestSuite::getInstance();
expect($plugin->coverage)->toBeFalse();

View File

@ -17,3 +17,23 @@ it('catch exceptions and messages', function () {
it('can just define the message', function () {
throw new Exception('Something bad happened');
})->throws('Something bad happened');
it('not catch exceptions if given condition is false', function () {
$this->assertTrue(true);
})->throwsIf(false, Exception::class);
it('catch exceptions if given condition is true', function () {
throw new Exception('Something bad happened');
})->throwsIf(function () { return true; }, Exception::class);
it('catch exceptions and messages if given condition is true', function () {
throw new Exception('Something bad happened');
})->throwsIf(true, Exception::class, 'Something bad happened');
it('can just define the message if given condition is true', function () {
throw new Exception('Something bad happened');
})->throwsIf(true, 'Something bad happened');
it('can just define the message if given condition is 1', function () {
throw new Exception('Something bad happened');
})->throwsIf(1, 'Something bad happened');

View File

@ -0,0 +1,148 @@
<?php
use PHPUnit\Framework\ExpectationFailedException;
beforeEach(function () {
$this->matched = null;
});
it('pass', function () {
expect('baz')
->match('foo', [
'bar' => function ($value) {
$this->matched = 'bar';
return $value->toEqual('bar');
},
'foo' => function ($value) {
$this->matched = 'baz';
return $value->toEqual('baz');
},
]
)
->toEqual($this->matched);
expect(static::getCount())->toBe(2);
});
it('failures', function () {
expect(true)
->match('foo', [
'bar' => function ($value) {
return $value->toBeTrue();
},
'foo' => function ($value) {
return $value->toBeFalse();
},
]
);
})->throws(ExpectationFailedException::class, 'true is false');
it('runs with truthy', function () {
expect('foo')
->match(1, [
'bar' => function ($value) {
$this->matched = 'bar';
return $value->toEqual('bar');
},
true => function ($value) {
$this->matched = 'foo';
return $value->toEqual('foo');
},
]
)
->toEqual($this->matched);
expect(static::getCount())->toBe(2);
});
it('runs with falsy', function () {
expect('foo')
->match(false, [
'bar' => function ($value) {
$this->matched = 'bar';
return $value->toEqual('bar');
},
false => function ($value) {
$this->matched = 'foo';
return $value->toEqual('foo');
},
]
)
->toEqual($this->matched);
expect(static::getCount())->toBe(2);
});
it('runs with truthy closure condition', function () {
expect('foo')
->match(
function () { return '1'; }, [
'bar' => function ($value) {
$this->matched = 'bar';
return $value->toEqual('bar');
},
true => function ($value) {
$this->matched = 'foo';
return $value->toEqual('foo');
},
]
)
->toEqual($this->matched);
expect(static::getCount())->toBe(2);
});
it('runs with falsy closure condition', function () {
expect('foo')
->match(
function () { return '0'; }, [
'bar' => function ($value) {
$this->matched = 'bar';
return $value->toEqual('bar');
},
false => function ($value) {
$this->matched = 'foo';
return $value->toEqual('foo');
},
]
)
->toEqual($this->matched);
expect(static::getCount())->toBe(2);
});
it('can be passed non-callable values', function () {
expect('foo')
->match('pest', [
'bar' => 'foo',
'pest' => 'baz',
]
);
})->throws(ExpectationFailedException::class, 'two strings are equal');
it('fails with unhandled match', function () {
expect('foo')->match('bar', []);
})->throws(ExpectationFailedException::class, 'Unhandled match value.');
it('can be used in higher order tests')
->expect(true)
->match(
function () { return true; }, [
false => function ($value) {
return $value->toBeFalse();
},
true => function ($value) {
return $value->toBeTrue();
},
]
);

View File

@ -1,5 +1,7 @@
<?php
use PHPUnit\Framework\ExpectationFailedException;
test('an exception is thrown if the the type is not iterable', function () {
expect('Foobar')->each->sequence();
})->throws(BadMethodCallException::class, 'Expectation value is not iterable.');
@ -7,9 +9,15 @@ test('an exception is thrown if the the type is not iterable', function () {
test('allows for sequences of checks to be run on iterable data', function () {
expect([1, 2, 3])
->sequence(
function ($expectation) { $expectation->toBeInt()->toEqual(1); },
function ($expectation) { $expectation->toBeInt()->toEqual(2); },
function ($expectation) { $expectation->toBeInt()->toEqual(3); },
function ($expectation) {
$expectation->toBeInt()->toEqual(1);
},
function ($expectation) {
$expectation->toBeInt()->toEqual(2);
},
function ($expectation) {
$expectation->toBeInt()->toEqual(3);
},
);
expect(static::getCount())->toBe(6);
@ -18,30 +26,46 @@ test('allows for sequences of checks to be run on iterable data', function () {
test('loops back to the start if it runs out of sequence items', function () {
expect([1, 2, 3, 1, 2, 3, 1, 2])
->sequence(
function ($expectation) { $expectation->toBeInt()->toEqual(1); },
function ($expectation) { $expectation->toBeInt()->toEqual(2); },
function ($expectation) { $expectation->toBeInt()->toEqual(3); },
function ($expectation) {
$expectation->toBeInt()->toEqual(1);
},
function ($expectation) {
$expectation->toBeInt()->toEqual(2);
},
function ($expectation) {
$expectation->toBeInt()->toEqual(3);
},
);
expect(static::getCount())->toBe(16);
});
test('it works if the number of items in the iterable is smaller than the number of expectations', function () {
test('fails if the number of iterable items is greater than the number of expectations', function () {
expect([1, 2])
->sequence(
function ($expectation) { $expectation->toBeInt()->toEqual(1); },
function ($expectation) { $expectation->toBeInt()->toEqual(2); },
function ($expectation) { $expectation->toBeInt()->toEqual(3); },
function ($expectation) {
$expectation->toBeInt()->toEqual(1);
},
function ($expectation) {
$expectation->toBeInt()->toEqual(2);
},
function ($expectation) {
$expectation->toBeInt()->toEqual(3);
},
);
expect(static::getCount())->toBe(4);
});
})->throws(ExpectationFailedException::class);
test('it works with associative arrays', function () {
expect(['foo' => 'bar', 'baz' => 'boom'])
->sequence(
function ($expectation, $key) { $expectation->toEqual('bar'); $key->toEqual('foo'); },
function ($expectation, $key) { $expectation->toEqual('boom'); $key->toEqual('baz'); },
function ($expectation, $key) {
$expectation->toEqual('bar');
$key->toEqual('foo');
},
function ($expectation, $key) {
$expectation->toEqual('boom');
$key->toEqual('baz');
},
);
});
@ -54,7 +78,9 @@ test('it can be passed non-callable values', function () {
test('it can be passed a mixture of value types', function () {
expect(['foo', 'bar', 'baz'])->sequence(
'foo',
function ($expectation) { $expectation->toEqual('bar')->toBeString(); },
function ($expectation) {
$expectation->toEqual('bar')->toBeString();
},
'baz'
);

View File

@ -5,7 +5,7 @@ use PHPUnit\Framework\ExpectationFailedException;
expect(true)->toBeTrue()->and(false)->toBeFalse();
test('strict comparisons', function () {
$nuno = new stdClass();
$nuno = new stdClass();
$dries = new stdClass();
expect($nuno)->toBe($nuno)->not->toBe($dries);

View File

@ -0,0 +1,27 @@
<?php
use PHPUnit\Framework\ExpectationFailedException;
it('passes', function ($value) {
expect($value)->toHaveLength(9);
})->with([
'Fortaleza', 'Sollefteå', 'Ιεράπετρα',
(object) [1, 2, 3, 4, 5, 6, 7, 8, 9],
collect([1, 2, 3, 4, 5, 6, 7, 8, 9]),
]);
it('passes with array', function () {
expect([1, 2, 3])->toHaveLength(3);
});
it('passes with *not*', function () {
expect('')->not->toHaveLength(1);
});
it('properly fails with *not*', function () {
expect('pest')->not->toHaveLength(4);
})->throws(ExpectationFailedException::class);
it('fails', function ($value) {
expect($value)->toHaveLength(1);
})->with([1, 1.5, true, null])->throws(BadMethodCallException::class);

View File

@ -0,0 +1,26 @@
<?php
use PHPUnit\Framework\ExpectationFailedException;
test('pass', function () {
$object = new stdClass();
$object->name = 'Jhon';
$object->age = 21;
expect($object)->toHaveProperties(['name', 'age']);
});
test('failures', function () {
$object = new stdClass();
$object->name = 'Jhon';
expect($object)->toHaveProperties(['name', 'age']);
})->throws(ExpectationFailedException::class);
test('not failures', function () {
$object = new stdClass();
$object->name = 'Jhon';
$object->age = 21;
expect($object)->not->toHaveProperties(['name', 'age']);
})->throws(ExpectationFailedException::class);

View File

@ -0,0 +1,72 @@
<?php
use PHPUnit\Framework\ExpectationFailedException;
test('passes', function () {
expect(function () { throw new RuntimeException(); })->toThrow(RuntimeException::class);
expect(function () { throw new RuntimeException(); })->toThrow(Exception::class);
expect(function () { throw new RuntimeException(); })->toThrow(function (RuntimeException $e) {});
expect(function () { throw new RuntimeException('actual message'); })->toThrow(function (Exception $e) {
expect($e->getMessage())->toBe('actual message');
});
expect(function () {})->not->toThrow(Exception::class);
expect(function () { throw new RuntimeException('actual message'); })->toThrow('actual message');
expect(function () { throw new Exception(); })->not->toThrow(RuntimeException::class);
expect(function () { throw new RuntimeException('actual message'); })->toThrow(RuntimeException::class, 'actual message');
expect(function () { throw new RuntimeException('actual message'); })->toThrow(function (RuntimeException $e) {}, 'actual message');
});
test('failures 1', function () {
expect(function () {})->toThrow(RuntimeException::class);
})->throws(ExpectationFailedException::class, 'Exception "' . RuntimeException::class . '" not thrown.');
test('failures 2', function () {
expect(function () {})->toThrow(function (RuntimeException $e) {});
})->throws(ExpectationFailedException::class, 'Exception "' . RuntimeException::class . '" not thrown.');
test('failures 3', function () {
expect(function () { throw new Exception(); })->toThrow(function (RuntimeException $e) {});
})->throws(ExpectationFailedException::class, 'Failed asserting that Exception Object');
test('failures 4', function () {
expect(function () { throw new Exception('actual message'); })
->toThrow(function (Exception $e) {
expect($e->getMessage())->toBe('expected message');
});
})->throws(ExpectationFailedException::class, 'Failed asserting that two strings are identical');
test('failures 5', function () {
expect(function () { throw new Exception('actual message'); })->toThrow('expected message');
})->throws(ExpectationFailedException::class, 'Failed asserting that \'actual message\' contains "expected message".');
test('failures 6', function () {
expect(function () {})->toThrow('actual message');
})->throws(ExpectationFailedException::class, 'Exception with message "actual message" not thrown');
test('failures 7', function () {
expect(function () { throw new RuntimeException('actual message'); })->toThrow(RuntimeException::class, 'expected message');
})->throws(ExpectationFailedException::class);
test('not failures', function () {
expect(function () { throw new RuntimeException(); })->not->toThrow(RuntimeException::class);
})->throws(ExpectationFailedException::class);
test('closure missing parameter', function () {
expect(function () {})->toThrow(function () {});
})->throws(InvalidArgumentException::class, 'The given closure must have a single parameter type-hinted as the class string.');
test('closure missing type-hint', function () {
expect(function () {})->toThrow(function ($e) {});
})->throws(InvalidArgumentException::class, 'The given closure\'s parameter must be type-hinted as the class string.');
it('can handle a non-defined exception', function () {
expect(function () {
throw new NonExistingException();
})->toThrow(NonExistingException::class);
})->throws(Error::class);
it('can handle a class not found Error', function () {
expect(function () {
throw new NonExistingException();
})->toThrow(Error::class);
});

View File

@ -0,0 +1,101 @@
<?php
use PHPUnit\Framework\ExpectationFailedException;
beforeEach(function () {
$this->unlessObject = new stdClass();
$this->unlessObject->trueValue = true;
$this->unlessObject->foo = 'foo';
});
it('pass', function () {
expect('foo')
->unless(
true,
function ($value) {
return $value->toEqual('bar');
}
)
->toEqual('foo');
expect(static::getCount())->toBe(1);
});
it('failures', function () {
expect('foo')
->unless(
false,
function ($value) {
return $value->toBeTrue();
}
)
->toEqual('foo');
})->throws(ExpectationFailedException::class, 'is true');
it('runs with truthy', function () {
expect($this->unlessObject)
->unless(
0,
function ($value) {
return $value->trueValue->toBeTrue();
}
)
->foo->toEqual('foo');
expect(static::getCount())->toBe(2);
});
it('skips with falsy', function () {
expect($this->unlessObject)
->unless(
1,
function ($value) {
return $value->trueValue->toBeFalse(); // fails
}
)
->unless(
true,
function ($value) {
return $value->trueValue->toBeFalse(); // fails
}
)
->foo->toEqual('foo');
expect(static::getCount())->toBe(1);
});
it('runs with truthy closure condition', function () {
expect($this->unlessObject)
->unless(
function () { return '0'; },
function ($value) {
return $value->trueValue->toBeTrue();
}
)
->foo->toEqual('foo');
expect(static::getCount())->toBe(2);
});
it('skips with falsy closure condition', function () {
expect($this->unlessObject)
->unless(
function () { return '1'; },
function ($value) {
return $value->trueValue->toBeFalse(); // fails
}
)
->foo->toEqual('foo');
expect(static::getCount())->toBe(1);
});
it('can be used in higher order tests')
->expect(true)
->unless(
function () { return false; },
function ($value) {
return $value->toBeFalse();
}
)
->throws(ExpectationFailedException::class, 'true is false');

View File

@ -0,0 +1,101 @@
<?php
use PHPUnit\Framework\ExpectationFailedException;
beforeEach(function () {
$this->whenObject = new stdClass();
$this->whenObject->trueValue = true;
$this->whenObject->foo = 'foo';
});
it('pass', function () {
expect('foo')
->when(
true,
function ($value) {
return $value->toEqual('foo');
}
)
->toEqual('foo');
expect(static::getCount())->toBe(2);
});
it('failures', function () {
expect('foo')
->when(
true,
function ($value) {
return $value->toBeTrue();
}
)
->toEqual('foo');
})->throws(ExpectationFailedException::class, 'is true');
it('runs with truthy', function () {
expect($this->whenObject)
->when(
1,
function ($value) {
return $value->trueValue->toBeTrue();
}
)
->foo->toEqual('foo');
expect(static::getCount())->toBe(2);
});
it('skips with falsy', function () {
expect($this->whenObject)
->when(
0,
function ($value) {
return $value->trueValue->toBeFalse(); // fails
}
)
->when(
false,
function ($value) {
return $value->trueValue->toBeFalse(); // fails
}
)
->foo->toEqual('foo');
expect(static::getCount())->toBe(1);
});
it('runs with truthy closure condition', function () {
expect($this->whenObject)
->when(
function () { return '1'; },
function ($value) {
return $value->trueValue->toBeTrue();
}
)
->foo->toEqual('foo');
expect(static::getCount())->toBe(2);
});
it('skips with falsy closure condition', function () {
expect($this->whenObject)
->when(
function () { return '0'; },
function ($value) {
return $value->trueValue->toBeFalse(); // fails
}
)
->foo->toEqual('foo');
expect(static::getCount())->toBe(1);
});
it('can be used in higher order tests')
->expect(false)
->when(
function () { return true; },
function ($value) {
return $value->toBeTrue();
}
)
->throws(ExpectationFailedException::class, 'false is true');

View File

@ -7,7 +7,7 @@ global $globalHook;
// HACK: we have to determine our $globalHook->calls baseline. This is because
// two other tests are executed before this one due to filename ordering.
$args = $_SERVER['argv'] ?? [];
$single = isset($args[1]) && Str::endsWith(__FILE__, $args[1]);
$single = (isset($args[1]) && Str::endsWith(__FILE__, $args[1])) || ($_SERVER['PEST_PARALLEL'] ?? false);
$offset = $single ? 0 : 2;
uses()->beforeAll(function () use ($globalHook, $offset) {

View File

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace Tests\CustomTestCase;
use function PHPUnit\Framework\assertTrue;
use PHPUnit\Framework\TestCase;
class CustomTestCase extends TestCase

View File

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace Tests\CustomTestCase;
use function PHPUnit\Framework\assertTrue;
use PHPUnit\Framework\TestCase;
class ExecutedTest extends TestCase

View File

@ -2,7 +2,7 @@
declare(strict_types=1);
namespace Tests\SubFolder\SubFolder\SubFolder;
namespace Tests\CustomTestCaseInSubFolders\SubFolder\SubFolder;
use PHPUnit\Framework\TestCase;

View File

@ -1,3 +0,0 @@
<?php
uses(Tests\SubFolder\SubFolder\SubFolder\CustomTestCaseInSubFolder::class)->in('CustomTestCaseInSubFolders/SubFolder');

View File

@ -1,5 +1,9 @@
<?php
use Tests\CustomTestCaseInSubFolders\SubFolder\SubFolder\CustomTestCaseInSubFolder;
uses(CustomTestCaseInSubFolder::class)->in('PHPUnit/CustomTestCaseInSubFolders/SubFolder/SubFolder');
uses()->group('integration')->in('Visual');
// NOTE: global test value container to be mutated and checked across files, as needed

View File

@ -23,7 +23,7 @@ test('default php unit tests', function () {
});
it('removes warnings', function () {
$testSuite = new TestSuite();
$testSuite = new TestSuite();
$warningTestCase = new WarningTestCase('No tests found in class "Pest\TestCase".');
$testSuite->addTest($warningTestCase);

View File

@ -0,0 +1,23 @@
<?php
use Pest\Plugins\Environment;
test('environment is set to CI when --ci option is used', function () {
$previousName = Environment::name();
$plugin = new Environment();
$plugin->handleArguments(['foo', '--ci', 'bar']);
expect(Environment::name())->toBe(Environment::CI);
Environment::name($previousName);
});
test('environment is set to Local when --ci option is not used', function () {
$plugin = new Environment();
$plugin->handleArguments(['foo', 'bar', 'baz']);
expect(Environment::name())->toBe(Environment::LOCAL);
});

View File

@ -1,7 +1,9 @@
<?php
use Pest\Plugins\Version;
use function Pest\version;
use Symfony\Component\Console\Output\BufferedOutput;
it('outputs the version when --version is used', function () {

View File

@ -10,7 +10,7 @@ it('gets file name from closure', function () {
});
it('gets property values', function () {
$class = new class() {
$class = new class() {
private $foo = 'bar';
};

View File

@ -2,13 +2,15 @@
use Pest\Exceptions\DatasetMissing;
use Pest\Exceptions\TestAlreadyExist;
use Pest\Factories\TestCaseFactory;
use Pest\Plugins\Environment;
use Pest\TestSuite;
it('does not allow to add the same test description twice', function () {
$testSuite = new TestSuite(getcwd(), 'tests');
$test = function () {};
$testSuite->tests->set(new \Pest\Factories\TestCaseFactory(__FILE__, 'foo', $test));
$testSuite->tests->set(new \Pest\Factories\TestCaseFactory(__FILE__, 'foo', $test));
$test = function () {};
$testSuite->tests->set(new TestCaseFactory(__FILE__, 'foo', $test));
$testSuite->tests->set(new TestCaseFactory(__FILE__, 'foo', $test));
})->throws(
TestAlreadyExist::class,
sprintf('A test with the description `%s` already exist in the filename `%s`.', 'foo', __FILE__),
@ -16,9 +18,57 @@ it('does not allow to add the same test description twice', function () {
it('alerts users about tests with arguments but no input', function () {
$testSuite = new TestSuite(getcwd(), 'tests');
$test = function (int $arg) {};
$testSuite->tests->set(new \Pest\Factories\TestCaseFactory(__FILE__, 'foo', $test));
$test = function (int $arg) {};
$testSuite->tests->set(new TestCaseFactory(__FILE__, 'foo', $test));
})->throws(
DatasetMissing::class,
sprintf("A test with the description '%s' has %d argument(s) ([%s]) and no dataset(s) provided in %s", 'foo', 1, 'int $arg', __FILE__),
);
it('can return an array of all test suite filenames', function () {
$testSuite = TestSuite::getInstance(getcwd(), 'tests');
$test = function () {};
$testSuite->tests->set(new TestCaseFactory(__FILE__, 'foo', $test));
$testSuite->tests->set(new TestCaseFactory(__FILE__, 'bar', $test));
expect($testSuite->tests->getFilenames())->toEqual([
__FILE__,
__FILE__,
]);
});
it('can filter the test suite filenames to those with the only method', function () {
$testSuite = new TestSuite(getcwd(), 'tests');
$test = function () {};
$testWithOnly = new TestCaseFactory(__FILE__, 'foo', $test);
$testWithOnly->only = true;
$testSuite->tests->set($testWithOnly);
$testSuite->tests->set(new TestCaseFactory('Baz/Bar/Boo.php', 'bar', $test));
expect($testSuite->tests->getFilenames())->toEqual([
__FILE__,
]);
});
it('does not filter the test suite filenames to those with the only method when working in CI pipeline', function () {
$previousEnvironment = Environment::name();
Environment::name(Environment::CI);
$testSuite = TestSuite::getInstance(getcwd(), 'tests');
$test = function () {};
$testWithOnly = new TestCaseFactory(__FILE__, 'foo', $test);
$testWithOnly->only = true;
$testSuite->tests->set($testWithOnly);
$testSuite->tests->set(new TestCaseFactory('Baz/Bar/Boo.php', 'bar', $test));
expect($testSuite->tests->getFilenames())->toEqual([
__FILE__,
'Baz/Bar/Boo.php',
]);
Environment::name($previousEnvironment);
});

View File

@ -8,7 +8,7 @@ test('visual snapshot of help command output', function () {
if (getenv('REBUILD_SNAPSHOTS')) {
$outputBuffer = new BufferedOutput();
$plugin = new Help($outputBuffer);
$plugin = new Help($outputBuffer);
$plugin();

View File

@ -2,14 +2,14 @@
test('visual snapshot of test suite on success', function () {
$testsPath = dirname(__DIR__);
$snapshot = implode(DIRECTORY_SEPARATOR, [
$snapshot = implode(DIRECTORY_SEPARATOR, [
$testsPath,
'.snapshots',
'success.txt',
]);
$output = function () use ($testsPath) {
$process = (new Symfony\Component\Process\Process(['php', 'bin/pest'], dirname($testsPath), ['EXCLUDE' => 'integration', 'REBUILD_SNAPSHOTS' => false]));
$process = (new Symfony\Component\Process\Process(['php', 'bin/pest'], dirname($testsPath), ['EXCLUDE' => 'integration', 'REBUILD_SNAPSHOTS' => false, 'PARATEST' => 0]));
$process->run();

0
tests/Visual/junit.html Normal file
View File