mirror of
https://github.com/pestphp/pest.git
synced 2026-07-28 20:38:57 +02:00
Compare commits
9 Commits
c6073511a2
..
v5.0.1
| Author | SHA1 | Date | |
|---|---|---|---|
| 6b2cd358e8 | |||
| e09e1be76d | |||
| 759ac63139 | |||
| 333f469efb | |||
| c4ec925aa5 | |||
| 5c8cfed483 | |||
| 962510b7e3 | |||
| e9c268a584 | |||
| fa91fcf744 |
@@ -28,7 +28,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
|
|
||||||
- name: Setup PHP
|
- name: Setup PHP
|
||||||
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2
|
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
|
|
||||||
- name: Setup PHP
|
- name: Setup PHP
|
||||||
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2
|
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2
|
||||||
|
|||||||
@@ -12,9 +12,9 @@
|
|||||||
|
|
||||||
------
|
------
|
||||||
|
|
||||||
> Pest v5 Now Available: **[Read the announcement »](https://pestphp.com/docs/pest-v5-is-here)**.
|
> Pest v5 Now Available: **[Read the announcement »](https://pestphp.com/docs/pest5-now-available)**.
|
||||||
|
|
||||||
**Pest** is an elegant PHP testing Framework with a focus on simplicity, meticulously designed to bring back the joy of testing in PHP.
|
**Pest** is an elegant testing framework for PHP developers and AI agents.
|
||||||
|
|
||||||
- Explore our docs at **[pestphp.com »](https://pestphp.com)**
|
- Explore our docs at **[pestphp.com »](https://pestphp.com)**
|
||||||
- Follow the creator Nuno Maduro:
|
- Follow the creator Nuno Maduro:
|
||||||
@@ -29,26 +29,12 @@
|
|||||||
|
|
||||||
We cannot thank our sponsors enough for their incredible support in funding Pest's development. Their contributions have been instrumental in making Pest the best it can be. For those who are interested in becoming a sponsor, please visit Nuno Maduro's Sponsor page at **[github.com/sponsors/nunomaduro](https://github.com/sponsors/nunomaduro)**.
|
We cannot thank our sponsors enough for their incredible support in funding Pest's development. Their contributions have been instrumental in making Pest the best it can be. For those who are interested in becoming a sponsor, please visit Nuno Maduro's Sponsor page at **[github.com/sponsors/nunomaduro](https://github.com/sponsors/nunomaduro)**.
|
||||||
|
|
||||||
|
|
||||||
### Platinum Sponsors
|
|
||||||
|
|
||||||
- **[CodeRabbit](https://coderabbit.ai/?ref=pestphp)**
|
|
||||||
- **[Mailtrap](https://l.rw.rw/pestphp)**
|
|
||||||
- **[SerpApi](https://serpapi.com/?ref=nunomaduro)**
|
|
||||||
- **[Tighten](https://tighten.com/?ref=nunomaduro)**
|
|
||||||
- **[Redberry](https://redberry.international/laravel-development/?utm_source=pest&utm_medium=banner&utm_campaign=pest_sponsorship)**
|
|
||||||
|
|
||||||
### Gold Sponsors
|
|
||||||
|
|
||||||
- **[CMS Max](https://cmsmax.com/?ref=pestphp)**
|
- **[CMS Max](https://cmsmax.com/?ref=pestphp)**
|
||||||
|
- **[PhpStorm](https://jb.gg/nuno)**
|
||||||
### Premium Sponsors
|
- **[CodeRabbit](https://coderabbit.link/nunomaduro)**
|
||||||
|
- **[SerpApi](https://serpapi.com/?ref=nunomaduro)**
|
||||||
- [Zapiet](https://zapiet.com/?ref=pestphp)
|
- **[Typesense](https://typesense.org/?ref=nunomaduro)**
|
||||||
- [Load Forge](https://loadforge.com/?ref=pestphp)
|
- **[Bento](https://bentonow.com/?ref=nunomaduro)**
|
||||||
- [Route4Me](https://route4me.com/pt?ref=pestphp)
|
- **[Redberry](https://redberry.international/laravel-development/)**
|
||||||
- [Nerdify](https://getnerdify.com/?ref=pestphp)
|
|
||||||
- [Akaunting](https://akaunting.com/?ref=pestphp)
|
|
||||||
- [TestMu AI](https://www.testmuai.com/?utm_medium=sponsor&utm_source=pest)
|
|
||||||
|
|
||||||
Pest is an open-sourced software licensed under the **[MIT license](https://opensource.org/licenses/MIT)**.
|
Pest is an open-sourced software licensed under the **[MIT license](https://opensource.org/licenses/MIT)**.
|
||||||
|
|||||||
@@ -0,0 +1,196 @@
|
|||||||
|
---
|
||||||
|
name: pest-testing
|
||||||
|
description: "Use this skill for Pest PHP testing in Laravel projects only. Trigger whenever any test is being written, edited, fixed, or refactored — including fixing tests that broke after a code change, adding assertions, converting PHPUnit to Pest, adding datasets, and TDD workflows. Always activate when the user asks how to write something in Pest, mentions test files or directories (tests/Feature, tests/Unit, tests/Browser), or needs browser testing, smoke testing multiple pages for JS errors, architecture tests, or faster test runs with Test Impact Analysis. Covers: test()/it()/expect() syntax, datasets, mocking, browser testing (visit/click/fill), smoke testing, arch(), Livewire component tests, RefreshDatabase, Tia (--tia), sharding, and all Pest 5 features. Do not use for factories, seeders, migrations, controllers, models, or non-test PHP code."
|
||||||
|
license: MIT
|
||||||
|
metadata:
|
||||||
|
author: laravel
|
||||||
|
---
|
||||||
|
@php
|
||||||
|
/** @var \Laravel\Boost\Install\GuidelineAssist $assist */
|
||||||
|
@endphp
|
||||||
|
# Pest Testing 5
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
Use `search-docs` for detailed Pest 5 patterns and documentation.
|
||||||
|
|
||||||
|
## Basic Usage
|
||||||
|
|
||||||
|
### Creating Tests
|
||||||
|
|
||||||
|
All tests must be written using Pest. Use `{{ $assist->artisanCommand('make:test --pest {name}') }}`.
|
||||||
|
|
||||||
|
The `{name}` argument should include only the path and test name, but should not include the test suite.
|
||||||
|
- Incorrect: `{{ $assist->artisanCommand('make:test --pest Feature/SomeFeatureTest') }}` will generate `tests/Feature/Feature/SomeFeatureTest.php`
|
||||||
|
- Correct: `{{ $assist->artisanCommand('make:test --pest SomeControllerTest') }}` will generate `tests/Feature/SomeControllerTest.php`
|
||||||
|
- Incorrect: `{{ $assist->artisanCommand('make:test --pest --unit Unit/SomeServiceTest') }}` will generate `tests/Unit/Unit/SomeServiceTest.php`
|
||||||
|
- Correct: `{{ $assist->artisanCommand('make:test --pest --unit SomeServiceTest') }}` will generate `tests/Unit/SomeServiceTest.php`
|
||||||
|
|
||||||
|
### Test Organization
|
||||||
|
|
||||||
|
- Unit/Feature tests: `tests/Feature` and `tests/Unit` directories.
|
||||||
|
- Browser tests: `tests/Browser/` directory.
|
||||||
|
- Do NOT remove tests without approval - these are core application code.
|
||||||
|
|
||||||
|
### Basic Test Structure
|
||||||
|
|
||||||
|
Pest supports both `test()` and `it()` functions. Before writing new tests, check existing test files in the same directory to match the project's convention. Use `test()` if existing tests use `test()`, or `it()` if they use `it()`.
|
||||||
|
|
||||||
|
@boostsnippet("Basic Pest Test Example", "php")
|
||||||
|
it('is true', function () {
|
||||||
|
expect(true)->toBeTrue();
|
||||||
|
});
|
||||||
|
@endboostsnippet
|
||||||
|
|
||||||
|
### Running Tests
|
||||||
|
|
||||||
|
- Run minimal tests with filter before finalizing: `{{ $assist->artisanCommand('test --compact --filter=testName') }}`.
|
||||||
|
- Run all tests: `{{ $assist->artisanCommand('test --compact') }}`.
|
||||||
|
- Run file: `{{ $assist->artisanCommand('test --compact tests/Feature/ExampleTest.php') }}`.
|
||||||
|
- Run only tests affected by recent changes (Tia): `./vendor/bin/pest --parallel --tia`.
|
||||||
|
|
||||||
|
## Assertions
|
||||||
|
|
||||||
|
Use specific assertions (`assertSuccessful()`, `assertNotFound()`) instead of `assertStatus()`:
|
||||||
|
|
||||||
|
@boostsnippet("Pest Response Assertion", "php")
|
||||||
|
it('returns all', function () {
|
||||||
|
$this->postJson('/api/docs', [])->assertSuccessful();
|
||||||
|
});
|
||||||
|
@endboostsnippet
|
||||||
|
|
||||||
|
| Use | Instead of |
|
||||||
|
|-----|------------|
|
||||||
|
| `assertSuccessful()` | `assertStatus(200)` |
|
||||||
|
| `assertNotFound()` | `assertStatus(404)` |
|
||||||
|
| `assertForbidden()` | `assertStatus(403)` |
|
||||||
|
|
||||||
|
## Mocking
|
||||||
|
|
||||||
|
Import mock function before use: `use function Pest\Laravel\mock;`
|
||||||
|
|
||||||
|
## Datasets
|
||||||
|
|
||||||
|
Use datasets for repetitive tests (validation rules, etc.):
|
||||||
|
|
||||||
|
@boostsnippet("Pest Dataset Example", "php")
|
||||||
|
it('has emails', function (string $email) {
|
||||||
|
expect($email)->not->toBeEmpty();
|
||||||
|
})->with([
|
||||||
|
'james' => 'james@laravel.com',
|
||||||
|
'taylor' => 'taylor@laravel.com',
|
||||||
|
]);
|
||||||
|
@endboostsnippet
|
||||||
|
|
||||||
|
## Pest 5 Features
|
||||||
|
|
||||||
|
| Feature | Purpose |
|
||||||
|
|---------|---------|
|
||||||
|
| Tia (Test Impact Analysis) | Rerun only tests affected by recent changes |
|
||||||
|
| Time-Balanced Sharding | Split tests across CI shards by execution time |
|
||||||
|
| New Validation Expectations | `toBeEmail()`, `toBeUlid()`, `toBeIpAddress()`, and more |
|
||||||
|
| Browser Testing | Full integration tests in real browsers |
|
||||||
|
| Smoke Testing | Validate multiple pages quickly |
|
||||||
|
| Visual Regression | Compare screenshots for visual changes |
|
||||||
|
| Architecture Testing | Enforce code conventions |
|
||||||
|
|
||||||
|
### Tia (Test Impact Analysis)
|
||||||
|
|
||||||
|
Tia reruns only tests affected by recent changes and replays cached results for the rest, dramatically reducing suite duration:
|
||||||
|
|
||||||
|
@boostsnippet("Tia Example", "shell")
|
||||||
|
./vendor/bin/pest --parallel --tia
|
||||||
|
@endboostsnippet
|
||||||
|
|
||||||
|
- Replayed tests are not skipped — cached tests store everything they produced, including covered lines and branches.
|
||||||
|
- Detects Laravel, Symfony, Livewire, and Inertia automatically.
|
||||||
|
|
||||||
|
### New Validation Expectations
|
||||||
|
|
||||||
|
Pest 5 ships eight new validation matchers, all supporting `.not` negation:
|
||||||
|
|
||||||
|
@boostsnippet("Pest 5 Validation Expectations", "php")
|
||||||
|
expect('nuno@pestphp.com')->toBeEmail();
|
||||||
|
expect('01ARZ3NDEKTSV4RRFFQ69G5FAV')->toBeUlid();
|
||||||
|
expect('192.168.1.1')->toBeIpAddress();
|
||||||
|
expect('00:1a:2b:3c:4d:5e')->toBeMacAddress();
|
||||||
|
expect('example.com')->toBeHostname();
|
||||||
|
expect('example.co.uk')->toBeDomain();
|
||||||
|
expect('Zm9vYmFy')->toBeBase64();
|
||||||
|
expect('deadbeef')->toBeHexadecimal();
|
||||||
|
@endboostsnippet
|
||||||
|
|
||||||
|
### Time-Balanced Sharding
|
||||||
|
|
||||||
|
Distribute tests across CI shards by execution time rather than count:
|
||||||
|
|
||||||
|
@boostsnippet("Pest Sharding Example", "shell")
|
||||||
|
./vendor/bin/pest --update-shards
|
||||||
|
./vendor/bin/pest --shard=1/4
|
||||||
|
@endboostsnippet
|
||||||
|
|
||||||
|
Commit `tests/.pest/shards.json` to the repository so CI shards stay consistent.
|
||||||
|
|
||||||
|
### Browser Test Example
|
||||||
|
|
||||||
|
Browser tests run in real browsers for full integration testing:
|
||||||
|
|
||||||
|
- Browser tests live in `tests/Browser/`.
|
||||||
|
- Use Laravel features like `Event::fake()`, `assertAuthenticated()`, and model factories.
|
||||||
|
- Use `RefreshDatabase` for clean state per test.
|
||||||
|
- Interact with page: click, type, scroll, select, submit, drag-and-drop, touch gestures.
|
||||||
|
- Test on multiple browsers (Chrome, Firefox, Safari) if requested.
|
||||||
|
- Test on different devices/viewports (iPhone 14 Pro, tablets) if requested.
|
||||||
|
- Switch color schemes (light/dark mode) when appropriate.
|
||||||
|
- Take screenshots or pause tests for debugging.
|
||||||
|
|
||||||
|
@boostsnippet("Pest Browser Test Example", "php")
|
||||||
|
it('may reset the password', function () {
|
||||||
|
Notification::fake();
|
||||||
|
|
||||||
|
$this->actingAs(User::factory()->create());
|
||||||
|
|
||||||
|
$page = visit('/sign-in');
|
||||||
|
|
||||||
|
$page->assertSee('Sign In')
|
||||||
|
->assertNoJavaScriptErrors()
|
||||||
|
->click('Forgot Password?')
|
||||||
|
->fill('email', 'nuno@laravel.com')
|
||||||
|
->click('Send Reset Link')
|
||||||
|
->assertSee('We have emailed your password reset link!');
|
||||||
|
|
||||||
|
Notification::assertSent(ResetPassword::class);
|
||||||
|
});
|
||||||
|
@endboostsnippet
|
||||||
|
|
||||||
|
### Smoke Testing
|
||||||
|
|
||||||
|
Quickly validate multiple pages have no JavaScript errors:
|
||||||
|
|
||||||
|
@boostsnippet("Pest Smoke Testing Example", "php")
|
||||||
|
$pages = visit(['/', '/about', '/contact']);
|
||||||
|
|
||||||
|
$pages->assertNoJavaScriptErrors()->assertNoConsoleLogs();
|
||||||
|
@endboostsnippet
|
||||||
|
|
||||||
|
### Visual Regression Testing
|
||||||
|
|
||||||
|
Capture and compare screenshots to detect visual changes.
|
||||||
|
|
||||||
|
### Architecture Testing
|
||||||
|
|
||||||
|
@boostsnippet("Architecture Test Example", "php")
|
||||||
|
arch('controllers')
|
||||||
|
->expect('App\Http\Controllers')
|
||||||
|
->toExtendNothing()
|
||||||
|
->toHaveSuffix('Controller');
|
||||||
|
@endboostsnippet
|
||||||
|
|
||||||
|
## Common Pitfalls
|
||||||
|
|
||||||
|
- Not importing `use function Pest\Laravel\mock;` before using mock
|
||||||
|
- Using `assertStatus(200)` instead of `assertSuccessful()`
|
||||||
|
- Forgetting datasets for repetitive validation tests
|
||||||
|
- Deleting tests without approval
|
||||||
|
- Forgetting `assertNoJavaScriptErrors()` in browser tests
|
||||||
|
- Prefixing `Feature/` or `Unit/` in `{name}` when using `make:test`
|
||||||
@@ -1219,4 +1219,104 @@ final class Expectation
|
|||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asserts that the value is an IP address.
|
||||||
|
*
|
||||||
|
* @return self<TValue>
|
||||||
|
*/
|
||||||
|
public function toBeIpAddress(string $message = ''): self
|
||||||
|
{
|
||||||
|
if (! is_string($this->value)) {
|
||||||
|
InvalidExpectationValue::expected('string');
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert::assertTrue((bool) filter_var($this->value, FILTER_VALIDATE_IP), $message);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asserts that the value is a MAC address.
|
||||||
|
*
|
||||||
|
* @return self<TValue>
|
||||||
|
*/
|
||||||
|
public function toBeMacAddress(string $message = ''): self
|
||||||
|
{
|
||||||
|
if (! is_string($this->value)) {
|
||||||
|
InvalidExpectationValue::expected('string');
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert::assertTrue((bool) filter_var($this->value, FILTER_VALIDATE_MAC), $message);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asserts that the value is a hostname.
|
||||||
|
*
|
||||||
|
* @return self<TValue>
|
||||||
|
*/
|
||||||
|
public function toBeHostname(string $message = ''): self
|
||||||
|
{
|
||||||
|
if (! is_string($this->value)) {
|
||||||
|
InvalidExpectationValue::expected('string');
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert::assertTrue((bool) filter_var($this->value, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME), $message);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asserts that the value is a domain name.
|
||||||
|
*
|
||||||
|
* @return self<TValue>
|
||||||
|
*/
|
||||||
|
public function toBeDomain(string $message = ''): self
|
||||||
|
{
|
||||||
|
if (! is_string($this->value)) {
|
||||||
|
InvalidExpectationValue::expected('string');
|
||||||
|
}
|
||||||
|
|
||||||
|
$isValid = filter_var($this->value, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME) !== false
|
||||||
|
&& str_contains($this->value, '.');
|
||||||
|
|
||||||
|
Assert::assertTrue($isValid, $message);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asserts that the value is a base64-encoded string.
|
||||||
|
*
|
||||||
|
* @return self<TValue>
|
||||||
|
*/
|
||||||
|
public function toBeBase64(string $message = ''): self
|
||||||
|
{
|
||||||
|
if (! is_string($this->value)) {
|
||||||
|
InvalidExpectationValue::expected('string');
|
||||||
|
}
|
||||||
|
|
||||||
|
$decoded = base64_decode($this->value, true);
|
||||||
|
Assert::assertTrue($decoded !== false && base64_encode($decoded) === $this->value, $message);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asserts that the value is a hexadecimal string.
|
||||||
|
*
|
||||||
|
* @return self<TValue>
|
||||||
|
*/
|
||||||
|
public function toBeHexadecimal(string $message = ''): self
|
||||||
|
{
|
||||||
|
if (! is_string($this->value)) {
|
||||||
|
InvalidExpectationValue::expected('string');
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert::assertTrue(ctype_xdigit($this->value), $message);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ use Pest\Plugins\Tia;
|
|||||||
use Pest\Plugins\Tia\Contracts\State;
|
use Pest\Plugins\Tia\Contracts\State;
|
||||||
use Pest\Support\Container;
|
use Pest\Support\Container;
|
||||||
use SebastianBergmann\CodeCoverage\CodeCoverage;
|
use SebastianBergmann\CodeCoverage\CodeCoverage;
|
||||||
|
use SebastianBergmann\CodeCoverage\Driver\Selector;
|
||||||
|
use SebastianBergmann\CodeCoverage\Filter;
|
||||||
|
use SebastianBergmann\CodeCoverage\Serialization\Unserializer;
|
||||||
use Throwable;
|
use Throwable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -161,7 +164,60 @@ final class CoverageMerger
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $value instanceof CodeCoverage ? $value : null;
|
// Legacy `--coverage-php` format: a serialized `CodeCoverage` object.
|
||||||
|
if ($value instanceof CodeCoverage) {
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since phpunit/php-code-coverage 14, `--coverage-php` writes the report
|
||||||
|
// as a serialized array (`['codeCoverage' => ..., 'testResults' => ...,
|
||||||
|
// 'basePath' => ...]`) rather than a `CodeCoverage` object, so it has to
|
||||||
|
// be rebuilt into one before it can be merged.
|
||||||
|
return self::coverageFromSerializedData($reportPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function coverageFromSerializedData(string $reportPath): ?CodeCoverage
|
||||||
|
{
|
||||||
|
if ($reportPath === '') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$serialized = new Unserializer()->unserialize($reportPath);
|
||||||
|
} catch (Throwable) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = $serialized['codeCoverage'];
|
||||||
|
$basePath = $serialized['basePath'];
|
||||||
|
|
||||||
|
if ($basePath !== '') {
|
||||||
|
foreach ($data->coveredFiles() as $relativePath) {
|
||||||
|
$data->renameFile($relativePath, $basePath.DIRECTORY_SEPARATOR.$relativePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$coverage = self::emptyCoverage();
|
||||||
|
|
||||||
|
if (! $coverage instanceof CodeCoverage) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$coverage->setData($data);
|
||||||
|
$coverage->setTests($serialized['testResults']);
|
||||||
|
|
||||||
|
return $coverage;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function emptyCoverage(): ?CodeCoverage
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$filter = new Filter;
|
||||||
|
|
||||||
|
return new CodeCoverage(new Selector()->forLineCoverage($filter), $filter);
|
||||||
|
} catch (Throwable) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static function unserializeCoverage(string $bytes): ?CodeCoverage
|
private static function unserializeCoverage(string $bytes): ?CodeCoverage
|
||||||
|
|||||||
@@ -476,6 +476,16 @@
|
|||||||
✓ pass
|
✓ pass
|
||||||
✓ failures
|
✓ failures
|
||||||
✓ failures with custom message
|
✓ failures with custom message
|
||||||
|
✓ not failures
|
||||||
|
|
||||||
|
PASS Tests\Features\Expect\toBeBase64
|
||||||
|
✓ pass
|
||||||
|
✓ failures
|
||||||
|
✓ failures with invalid padding
|
||||||
|
✓ failures with padding only
|
||||||
|
✓ failures with malformed input
|
||||||
|
✓ failures with invalid type
|
||||||
|
✓ failures with custom message
|
||||||
✓ not failures
|
✓ not failures
|
||||||
|
|
||||||
PASS Tests\Features\Expect\toBeBetween
|
PASS Tests\Features\Expect\toBeBetween
|
||||||
@@ -522,6 +532,14 @@
|
|||||||
✓ pass
|
✓ pass
|
||||||
✓ failures
|
✓ failures
|
||||||
✓ failures with custom message
|
✓ failures with custom message
|
||||||
|
✓ not failures
|
||||||
|
|
||||||
|
PASS Tests\Features\Expect\toBeDomain
|
||||||
|
✓ pass
|
||||||
|
✓ failures
|
||||||
|
✓ failures with leading dot
|
||||||
|
✓ failures with invalid type
|
||||||
|
✓ failures with custom message
|
||||||
✓ not failures
|
✓ not failures
|
||||||
|
|
||||||
PASS Tests\Features\Expect\toBeEmail
|
PASS Tests\Features\Expect\toBeEmail
|
||||||
@@ -584,6 +602,24 @@
|
|||||||
✓ passes with strings
|
✓ passes with strings
|
||||||
✓ failures
|
✓ failures
|
||||||
✓ failures with custom message
|
✓ failures with custom message
|
||||||
|
✓ not failures
|
||||||
|
|
||||||
|
PASS Tests\Features\Expect\toBeHexadecimal
|
||||||
|
✓ pass
|
||||||
|
✓ failures
|
||||||
|
✓ failures with invalid characters
|
||||||
|
✓ failures with zero-x prefix
|
||||||
|
✓ failures with empty string
|
||||||
|
✓ failures with invalid type
|
||||||
|
✓ failures with custom message
|
||||||
|
✓ not failures
|
||||||
|
|
||||||
|
PASS Tests\Features\Expect\toBeHostname
|
||||||
|
✓ pass
|
||||||
|
✓ failures
|
||||||
|
✓ failures with trailing hyphen
|
||||||
|
✓ failures with invalid type
|
||||||
|
✓ failures with custom message
|
||||||
✓ not failures
|
✓ not failures
|
||||||
|
|
||||||
PASS Tests\Features\Expect\toBeIn
|
PASS Tests\Features\Expect\toBeIn
|
||||||
@@ -622,6 +658,13 @@
|
|||||||
✓ failure when the class is not invokable
|
✓ failure when the class is not invokable
|
||||||
✓ class is not invokable
|
✓ class is not invokable
|
||||||
|
|
||||||
|
PASS Tests\Features\Expect\toBeIpAddress
|
||||||
|
✓ pass
|
||||||
|
✓ failures
|
||||||
|
✓ failures with invalid type
|
||||||
|
✓ failures with custom message
|
||||||
|
✓ not failures
|
||||||
|
|
||||||
PASS Tests\Features\Expect\toBeIterable
|
PASS Tests\Features\Expect\toBeIterable
|
||||||
✓ pass
|
✓ pass
|
||||||
✓ failures
|
✓ failures
|
||||||
@@ -666,6 +709,13 @@
|
|||||||
✓ pass
|
✓ pass
|
||||||
✓ failures
|
✓ failures
|
||||||
✓ failures with custom message
|
✓ failures with custom message
|
||||||
|
✓ not failures
|
||||||
|
|
||||||
|
PASS Tests\Features\Expect\toBeMacAddress
|
||||||
|
✓ pass
|
||||||
|
✓ failures
|
||||||
|
✓ failures with invalid type
|
||||||
|
✓ failures with custom message
|
||||||
✓ not failures
|
✓ not failures
|
||||||
|
|
||||||
PASS Tests\Features\Expect\toBeNAN
|
PASS Tests\Features\Expect\toBeNAN
|
||||||
@@ -2136,4 +2186,4 @@
|
|||||||
✓ pass with dataset with ('my-datas-set-value')
|
✓ pass with dataset with ('my-datas-set-value')
|
||||||
✓ within describe → pass with dataset with ('my-datas-set-value')
|
✓ within describe → pass with dataset with ('my-datas-set-value')
|
||||||
|
|
||||||
Tests: 1 deprecated, 4 warnings, 5 incomplete, 2 notices, 40 todos, 35 skipped, 1504 passed (3281 assertions)
|
Tests: 1 deprecated, 4 warnings, 5 incomplete, 2 notices, 40 todos, 35 skipped, 1542 passed (3375 assertions)
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Pest\Exceptions\InvalidExpectationValue;
|
||||||
|
use PHPUnit\Framework\ExpectationFailedException;
|
||||||
|
|
||||||
|
test('pass', function (): void {
|
||||||
|
expect('Zm9v')->toBeBase64() // 'foo' (no padding)
|
||||||
|
->and('Zm9vYg==')->toBeBase64() // 'foob' (with padding)
|
||||||
|
->and('Zm9vYmE=')->toBeBase64() // 'fooba' (with padding)
|
||||||
|
->and('Zm9vYmFy')->toBeBase64() // 'foobar' (no padding)
|
||||||
|
->and('')->toBeBase64(); // empty string
|
||||||
|
});
|
||||||
|
|
||||||
|
test('failures', function (): void {
|
||||||
|
expect('not-base64!')->toBeBase64();
|
||||||
|
})->throws(ExpectationFailedException::class);
|
||||||
|
|
||||||
|
test('failures with invalid padding', function (): void {
|
||||||
|
expect('AAA')->toBeBase64();
|
||||||
|
})->throws(ExpectationFailedException::class);
|
||||||
|
|
||||||
|
test('failures with padding only', function (): void {
|
||||||
|
expect('====')->toBeBase64();
|
||||||
|
})->throws(ExpectationFailedException::class);
|
||||||
|
|
||||||
|
test('failures with malformed input', function (): void {
|
||||||
|
expect('!!invalid!!')->toBeBase64();
|
||||||
|
})->throws(ExpectationFailedException::class);
|
||||||
|
|
||||||
|
test('failures with invalid type', function (): void {
|
||||||
|
expect([])->toBeBase64();
|
||||||
|
})->throws(InvalidExpectationValue::class, 'Invalid expectation value type. Expected [string].');
|
||||||
|
|
||||||
|
test('failures with custom message', function (): void {
|
||||||
|
expect('!!invalid!!')->toBeBase64('oh no!');
|
||||||
|
})->throws(ExpectationFailedException::class, 'oh no!');
|
||||||
|
|
||||||
|
test('not failures', function (): void {
|
||||||
|
expect('Zm9v')->not->toBeBase64();
|
||||||
|
})->throws(ExpectationFailedException::class);
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Pest\Exceptions\InvalidExpectationValue;
|
||||||
|
use PHPUnit\Framework\ExpectationFailedException;
|
||||||
|
|
||||||
|
test('pass', function (): void {
|
||||||
|
expect('example.com')->toBeDomain() // standard domain
|
||||||
|
->and('sub.example.com')->toBeDomain() // subdomain
|
||||||
|
->and('my-host.io')->toBeDomain() // with hyphen
|
||||||
|
->and('example.co.uk')->toBeDomain(); // multi-level TLD
|
||||||
|
});
|
||||||
|
|
||||||
|
test('failures', function (): void {
|
||||||
|
expect('example')->toBeDomain();
|
||||||
|
})->throws(ExpectationFailedException::class);
|
||||||
|
|
||||||
|
test('failures with leading dot', function (): void {
|
||||||
|
expect('.example.com')->toBeDomain();
|
||||||
|
})->throws(ExpectationFailedException::class);
|
||||||
|
|
||||||
|
test('failures with invalid type', function (): void {
|
||||||
|
expect([])->toBeDomain();
|
||||||
|
})->throws(InvalidExpectationValue::class, 'Invalid expectation value type. Expected [string].');
|
||||||
|
|
||||||
|
test('failures with custom message', function (): void {
|
||||||
|
expect('example')->toBeDomain('oh no!');
|
||||||
|
})->throws(ExpectationFailedException::class, 'oh no!');
|
||||||
|
|
||||||
|
test('not failures', function (): void {
|
||||||
|
expect('example.com')->not->toBeDomain();
|
||||||
|
})->throws(ExpectationFailedException::class);
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Pest\Exceptions\InvalidExpectationValue;
|
||||||
|
use PHPUnit\Framework\ExpectationFailedException;
|
||||||
|
|
||||||
|
test('pass', function (): void {
|
||||||
|
expect('abcdef')->toBeHexadecimal() // lowercase
|
||||||
|
->and('ABCDEF')->toBeHexadecimal() // uppercase
|
||||||
|
->and('aBcDeF')->toBeHexadecimal() // mixed case
|
||||||
|
->and('1234567890')->toBeHexadecimal() // numeric
|
||||||
|
->and('deadbeef')->toBeHexadecimal(); // alphanumeric
|
||||||
|
});
|
||||||
|
|
||||||
|
test('failures', function (): void {
|
||||||
|
expect('xyz')->toBeHexadecimal();
|
||||||
|
})->throws(ExpectationFailedException::class);
|
||||||
|
|
||||||
|
test('failures with invalid characters', function (): void {
|
||||||
|
expect('ghijkl')->toBeHexadecimal();
|
||||||
|
})->throws(ExpectationFailedException::class);
|
||||||
|
|
||||||
|
test('failures with zero-x prefix', function (): void {
|
||||||
|
expect('0x1a')->toBeHexadecimal();
|
||||||
|
})->throws(ExpectationFailedException::class);
|
||||||
|
|
||||||
|
test('failures with empty string', function (): void {
|
||||||
|
expect('')->toBeHexadecimal();
|
||||||
|
})->throws(ExpectationFailedException::class);
|
||||||
|
|
||||||
|
test('failures with invalid type', function (): void {
|
||||||
|
expect([])->toBeHexadecimal();
|
||||||
|
})->throws(InvalidExpectationValue::class, 'Invalid expectation value type. Expected [string].');
|
||||||
|
|
||||||
|
test('failures with custom message', function (): void {
|
||||||
|
expect('xyz')->toBeHexadecimal('oh no!');
|
||||||
|
})->throws(ExpectationFailedException::class, 'oh no!');
|
||||||
|
|
||||||
|
test('not failures', function (): void {
|
||||||
|
expect('abcdef')->not->toBeHexadecimal();
|
||||||
|
})->throws(ExpectationFailedException::class);
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Pest\Exceptions\InvalidExpectationValue;
|
||||||
|
use PHPUnit\Framework\ExpectationFailedException;
|
||||||
|
|
||||||
|
test('pass', function (): void {
|
||||||
|
expect('example')->toBeHostname() // single label
|
||||||
|
->and('example.com')->toBeHostname() // multiple labels
|
||||||
|
->and('sub.example.com')->toBeHostname() // subdomain
|
||||||
|
->and('my-host')->toBeHostname(); // with hyphen
|
||||||
|
});
|
||||||
|
|
||||||
|
test('failures', function (): void {
|
||||||
|
expect('-example')->toBeHostname();
|
||||||
|
})->throws(ExpectationFailedException::class);
|
||||||
|
|
||||||
|
test('failures with trailing hyphen', function (): void {
|
||||||
|
expect('example-')->toBeHostname();
|
||||||
|
})->throws(ExpectationFailedException::class);
|
||||||
|
|
||||||
|
test('failures with invalid type', function (): void {
|
||||||
|
expect([])->toBeHostname();
|
||||||
|
})->throws(InvalidExpectationValue::class, 'Invalid expectation value type. Expected [string].');
|
||||||
|
|
||||||
|
test('failures with custom message', function (): void {
|
||||||
|
expect('-example')->toBeHostname('oh no!');
|
||||||
|
})->throws(ExpectationFailedException::class, 'oh no!');
|
||||||
|
|
||||||
|
test('not failures', function (): void {
|
||||||
|
expect('example.com')->not->toBeHostname();
|
||||||
|
})->throws(ExpectationFailedException::class);
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Pest\Exceptions\InvalidExpectationValue;
|
||||||
|
use PHPUnit\Framework\ExpectationFailedException;
|
||||||
|
|
||||||
|
test('pass', function (): void {
|
||||||
|
expect('192.168.1.1')->toBeIpAddress()
|
||||||
|
->and('::1')->toBeIpAddress()
|
||||||
|
->and('2001:db8::1')->toBeIpAddress();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('failures', function (): void {
|
||||||
|
expect('not-an-ip')->toBeIpAddress();
|
||||||
|
})->throws(ExpectationFailedException::class);
|
||||||
|
|
||||||
|
test('failures with invalid type', function (): void {
|
||||||
|
expect([])->toBeIpAddress();
|
||||||
|
})->throws(InvalidExpectationValue::class, 'Invalid expectation value type. Expected [string].');
|
||||||
|
|
||||||
|
test('failures with custom message', function (): void {
|
||||||
|
expect('not-an-ip')->toBeIpAddress('oh no!');
|
||||||
|
})->throws(ExpectationFailedException::class, 'oh no!');
|
||||||
|
|
||||||
|
test('not failures', function (): void {
|
||||||
|
expect('192.168.1.1')->not->toBeIpAddress();
|
||||||
|
})->throws(ExpectationFailedException::class);
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Pest\Exceptions\InvalidExpectationValue;
|
||||||
|
use PHPUnit\Framework\ExpectationFailedException;
|
||||||
|
|
||||||
|
test('pass', function (): void {
|
||||||
|
expect('00:1a:2b:3c:4d:5e')->toBeMacAddress() // colon-separated
|
||||||
|
->and('00-1a-2b-3c-4d-5e')->toBeMacAddress() // hyphen-separated
|
||||||
|
->and('ff:ff:ff:ff:ff:ff')->toBeMacAddress();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('failures', function (): void {
|
||||||
|
expect('not-a-mac')->toBeMacAddress();
|
||||||
|
})->throws(ExpectationFailedException::class);
|
||||||
|
|
||||||
|
test('failures with invalid type', function (): void {
|
||||||
|
expect([])->toBeMacAddress();
|
||||||
|
})->throws(InvalidExpectationValue::class, 'Invalid expectation value type. Expected [string].');
|
||||||
|
|
||||||
|
test('failures with custom message', function (): void {
|
||||||
|
expect('not-a-mac')->toBeMacAddress('oh no!');
|
||||||
|
})->throws(ExpectationFailedException::class, 'oh no!');
|
||||||
|
|
||||||
|
test('not failures', function (): void {
|
||||||
|
expect('00:1a:2b:3c:4d:5e')->not->toBeMacAddress();
|
||||||
|
})->throws(ExpectationFailedException::class);
|
||||||
@@ -24,13 +24,13 @@ test('parallel', function () use ($run): void {
|
|||||||
$file = file_get_contents(__FILE__);
|
$file = file_get_contents(__FILE__);
|
||||||
$file = preg_replace(
|
$file = preg_replace(
|
||||||
'/\$expected = \'.*?\';/',
|
'/\$expected = \'.*?\';/',
|
||||||
"\$expected = '1 deprecated, 4 warnings, 5 incomplete, 3 notices, 40 todos, 27 skipped, 1487 passed (3228 assertions)';",
|
"\$expected = '1 deprecated, 4 warnings, 5 incomplete, 3 notices, 40 todos, 27 skipped, 1525 passed (3322 assertions)';",
|
||||||
$file,
|
$file,
|
||||||
);
|
);
|
||||||
file_put_contents(__FILE__, $file);
|
file_put_contents(__FILE__, $file);
|
||||||
}
|
}
|
||||||
|
|
||||||
$expected = '1 deprecated, 4 warnings, 5 incomplete, 3 notices, 40 todos, 27 skipped, 1487 passed (3228 assertions)';
|
$expected = '1 deprecated, 4 warnings, 5 incomplete, 3 notices, 40 todos, 27 skipped, 1525 passed (3322 assertions)';
|
||||||
|
|
||||||
expect($output)
|
expect($output)
|
||||||
->toContain("Tests: {$expected}")
|
->toContain("Tests: {$expected}")
|
||||||
|
|||||||
Reference in New Issue
Block a user