pest
Skill0 starsChanged 28 days agoMITView on GitHub
Pest PHP — framework de test élégant pour PHP. Expectation API, datasets, hooks, architecture testing, mocking, snapshot testing, browser testing, mutation testing, CI/CD, plugins. Utiliser quand on écrit des tests PHP, on configure Pest, on veut améliorer la couverture, ou on debug un test.
- Installs packages
In this repository29 other files it gives its agents.
- AGENTS.mdAGENTS.md
- skills/laravel-core/templates/AGENTS.mdAGENTS.md
- CLAUDE.mdCLAUDE.md
- dokploy-deploySkill
- laravel-adminSkill
- laravel-aiSkill
- laravel-apiSkill
- laravel-coreSkill
- laravel-deploySkill
- laravel-inertiaSkill
- laravel-livewireSkill
- laravel-mcpSkill
- laravel-microserviceSkill
- laravel-multi-tenancySkill
- laravel-nuxtSkill
- laravel-performanceSkill
- laravel-queueSkill
- laravel-securitySkill
- laravel-testingSkill
- laravel-websocketSkill
- nuxt-coreSkill
- nuxt-uiSkill
- project-definerSkill
- scribeSkill
- searxng-mcpSkill
- sentrySkill
- skill-creatorSkill
- skill-health-checkerSkill
- spatie-query-builderSkill
The file
Files
Pest PHP — Testing Framework
Tu es un expert Pest PHP. Ton rôle est de guider l'écriture de tests élégants et efficaces avec Pest.
Quand utiliser
- Écrire des tests PHP avec Pest
- Configurer le test suite
- Utiliser des datasets, hooks, mocking
- Faire du architecture testing
- Configurer le CI/CD pour les tests
- Optimiser les tests (parallel, TIA, profiling)
Quand NE PAS utiliser
- Tests avec PHPUnit pur (sans Pest)
- Configuration de serveur
- Code de production (pas de test)
Installation
composer require pestphp/pest --dev --with-all-dependencies
./vendor/bin/pest --initStructure
tests/
├── Unit/
│ └── ExampleTest.php
├── Feature/
│ └── ExampleTest.php
├── Datasets/
│ └── Emails.php
├── Pest.php
└── TestCase.phpSyntaxe de base
<?php
// test() — nom explicite
test('sum', function () {
$result = sum(1, 2);
expect($result)->toBe(3);
});
// it() — préfixe "it"
it('performs sums', function () {
$result = sum(1, 2);
expect($result)->toBe(3);
});
// describe() — grouper les tests
describe('sum', function () {
it('may sum integers', function () {
expect(sum(1, 2))->toBe(3);
});
it('may sum floats', function () {
expect(sum(1.5, 2.5))->toBe(4.0);
});
});Expectation API
<?php
// Comparaisons
expect($value)->toBe(3); // strict equality
expect($value)->toEqual(3); // loose equality
expect($value)->toBeInt();
expect($value)->toBeString();
expect($value)->toBeTrue();
expect($value)->toBeFalse();
expect($value)->toBeNull();
expect($value)->toBeEmpty();
expect($value)->toBeArray();
expect($value)->toBeObject();
expect($value)->toBeFloat();
expect($value)->toBeBool();
expect($value)->toBeNumeric();
expect($value)->toBeEmail();
expect($value)->toBeUrl();
expect($value)->toBeUuid();
expect($value)->toBeJson();
// Contenu
expect($array)->toContain(2, 4);
expect($string)->toContain('Hello');
expect($string)->toStartWith('Hello');
expect($string)->toEndWith('World');
expect($string)->toMatch('/pattern/');
// Taille
expect($array)->toHaveCount(4);
expect($string)->toHaveLength(5);
// Clés
expect($array)->toHaveKey('name');
expect($array)->toHaveKeys(['id', 'name']);
// Objets
expect($user)->toHaveProperty('name');
expect($user)->toHaveProperty('name', 'Nuno');
expect($user)->toBeInstanceOf(User::class);
// Comparaison d'objets
expect($user)->toMatchObject(['name' => 'Nuno']);
// Exceptions
expect(fn () => throw new Exception('msg'))->toThrow(Exception::class);
expect(fn () => throw new Exception('msg'))->toThrow('msg');
// Negation
expect($value)->not->toBe(3);
expect($value)->not->toBeString();
// Chainage
expect($value)
->toBeInt()
->toBeGreaterThan(0)
->toBeLessThan(100);
// and() — verifier deux valeurs
expect($id)->toBe(14)->and($name)->toBe('Nuno');
// each() — verifier chaque element
expect([1, 2, 3])->each->toBeInt();
// sequence() — verifier dans l'ordre
expect([1, 2, 3])->sequence(
fn ($n) => $n->toBe(1),
fn ($n) => $n->toBe(2),
fn ($n) => $n->toBe(3),
);
// json() — decoder et tester
expect('{"name":"Nuno"}')
->json()
->name->toBe('Nuno');
// Modifiers
expect($value)->when($condition, fn ($v) => $v->toBe(1));
expect($value)->unless($condition, fn ($v) => $v->toBe(2));Hooks
<?php
// beforeEach — avant chaque test
beforeEach(function () {
$this->user = User::factory()->create();
});
// afterEach — apres chaque test
afterEach(function () {
// cleanup
});
// beforeAll — une fois avant tous les tests du fichier
beforeAll(function () {
// setup une seule fois (pas de $this)
});
// afterAll — une fois apres tous les tests
afterAll(function () {
// cleanup une seule fois
});
// after() — cleanup specifique a un test
it('creates a user', function () {
// ...
})->after(function () {
// cleanup specifique
});Datasets
<?php
// Dataset inline
it('has emails', function (string $email) {
expect($email)->not->toBeEmpty();
})->with(['enunomaduro@gmail.com', 'other@example.com']);
// Dataset avec cles
it('has emails', function (string $email) {
expect($email)->not->toBeEmpty();
})->with([
'james' => 'james@laravel.com',
'taylor' => 'taylor@laravel.com',
]);
// Dataset partage (tests/Datasets/Emails.php)
dataset('emails', [
'enunomaduro@gmail.com',
'other@example.com',
]);
it('has emails', function (string $email) {
expect($email)->not->toBeEmpty();
})->with('emails');
// Dataset avec closures (lazy evaluation)
it('can generate user', function (User $user) {
expect($user)->toBeInstanceOf(User::class);
})->with([
fn () => User::factory()->create(['name' => 'Nuno']),
fn () => User::factory()->create(['name' => 'Luke']),
]);
// Cartesian product
dataset('days', ['Saturday', 'Sunday']);
test('business is closed', function (string $business, string $day) {
expect(new $business)->isClosed($day)->toBeTrue();
})->with([Office::class, Bank::class])->with('days');Exceptions
<?php
it('throws exception', function () {
throw new Exception('Something happened.');
})->throws(Exception::class);
it('throws with message', function () {
throw new Exception('Something happened.');
})->throws(Exception::class, 'Something happened.');
it('throws only message', function () {
throw new Exception('Something happened.');
})->throws('Something happened.');
it('throws conditionally', function () {
// ...
})->throwsIf(fn () => DB::getDriverName() === 'mysql', Exception::class);
it('throws no exceptions', function () {
$result = 1 + 1;
})->throwsNoExceptions();Filtering
<?php
// --bail : stop au premier echec
./vendor/bin/pest --bail
// --dirty : tests avec changements non commits
./vendor/bin/pest --dirty
// --filter : regex sur les tests
./vendor/bin/pest --filter "test description"
// --group / --exclude-group
./vendor/bin/pest --group=integration
./vendor/bin/pest --exclude-group=slow
// --retry : re-run les tests echoues en premier
./vendor/bin/pest --retry
// only() : focus sur un test
pest()->only(); // fichier
test('sum', function () { ... })->only(); // test specifique
// --flaky : retry automatique des tests instables
it('may fail', function () {
// ...
})->flaky(tries: 3);Skipping
<?php
it('has home', function () {
// ...
})->skip();
it('has home', function () {
// ...
})->skip('temporarily unavailable');
it('has home', function () {
// ...
})->skip(fn () => DB::getDriverName() !== 'mysql', 'db not supported');
it('has home', function () {
// ...
})->skipLocally();
it('has home', function () {
// ...
})->skipOnWindows();
// todo() : marquer comme a faire
it('has home', function () {
// ...
})->todo();Mocking
<?php
use Mockery;
it('may buy a book', function () {
$client = Mockery::mock(PaymentClient::class);
$client->shouldReceive('post')->andReturn('response');
$books = new BookRepository($client);
$books->buy();
});
// Argument expectations
$client->shouldReceive('post')->with(1, Mockery::any());
// Return values
$client->shouldReceive('post')->andReturn(1, 2);
$client->shouldReceive('post')->andThrow(new Exception);
// Count expectations
$mock->shouldReceive('post')->once();
$mock->shouldReceive('post')->times(3);Snapshot Testing
<?php
it('has a contact page', function () {
$response = $this->get('/contact');
expect($response)->toMatchSnapshot();
});
// Mettre a jour les snapshots
./vendor/bin/pest --update-snapshotsArchitecture Testing
<?php
arch()
->expect('App')
->toUseStrictTypes()
->not->toUse(['die', 'dd', 'dump']);
arch()
->expect('App\Models')
->toBeClasses()
->toExtend('Illuminate\Database\Eloquent\Model');
arch()
->expect('App\Http')
->toOnlyBeUsedIn('App\Http');
// Presets
arch()->preset()->php();
arch()->preset()->security();
arch()->preset()->laravel();
arch()->preset()->strict();
// Modifiers
arch()
->expect('App\Models')
->classes()
->toBeFinal();
arch()
->expect('App\Services')
->ignoring('App\Support');Custom Expectations
<?php
// tests/Pest.php ou tests/Expectations.php
expect()->extend('toBeWithinRange', function (int $min, int $max) {
return $this->toBeGreaterThanOrEqual($min)
->toBeLessThanOrEqual($max);
});
// Utilisation
expect(100)->toBeWithinRange(90, 110);
// Intercepter une expectation existante
expect()->intercept('toBe', Model::class, function (Model $expected) {
expect($this->value->id)->toBe($expected->id);
});Configuration (Pest.php)
<?php
// tests/Pest.php
// Base test case
pest()->extend(TestCase::class)->in('Feature');
// Traits
pest()->use(RefreshDatabase::class)->in('Feature');
// Groupes
pest()->group('integration')->in('Feature');
// Hooks globaux
pest()->beforeEach(function () {
// avant chaque test
})->in('Feature');
// Browser testing
pest()->browser()->timeout(10000);
pest()->browser()->headed();
// TIA (Test Impact Analysis)
pest()->tia()->locally();Parallel Testing
# Parallele
./vendor/bin/pest --parallel
# Nombre de processus
./vendor/bin/pest --parallel --processes=10
# Sharding
./vendor/bin/pest --shard=1/4
# Time-balanced sharding
./vendor/bin/pest --update-shards
./vendor/bin/pest --shard=1/4CI/CD
# GitHub Actions
name: Tests
on: ['push', 'pull_request']
jobs:
ci:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: shivammathur/setup-php@v2
with:
php-version: 8.4
coverage: xdebug
- run: composer install --no-interaction
- run: ./vendor/bin/pest --ciCode Coverage
# Coverage
./vendor/bin/pest --coverage
# Minimum threshold
./vendor/bin/pest --coverage --min=90
# Exact threshold
./vendor/bin/pest --coverage --exactly=99.3
# Formats
./vendor/bin/pest --coverage-html=report
./vendor/bin/pest --coverage-clover=report.xmlMutation Testing
<?php
// Marquer ce qui est teste
covers(TodoController::class);
it('list todos', function () {
$this->getJson('/todos')->assertStatus(200);
});# Lancer
./vendor/bin/pest --mutate
./vendor/bin/pest --mutate --parallel
./vendor/bin/pest --mutate --min=80Plugins officiels
# Laravel plugin
composer require pestphp/pest-plugin-laravel --dev
# Livewire plugin
composer require pestphp/pest-plugin-livewire --dev
# Browser testing
composer require pestphp/pest-plugin-browser --dev
npm install playwright@latest
# Faker
composer require pestphp/pest-plugin-faker --dev
# PHPStan
composer require pestphp/pest-plugin-phpstan --dev
# Rector
composer require pestphp/pest-plugin-rector --dev
# Type Coverage
composer require pestphp/pest-plugin-type-coverage --dev
# Evals (AI testing)
composer require pestphp/pest-plugin-evals --dev
# Stress Testing
composer require pestphp/pest-plugin-stressless --dev
# Agent (verification AI)
composer require pestphp/pest-plugin-agent --dev
# Profanity check
composer require pestphp/pest-plugin-profanity --devBonnes pratiques
- Préférer
it()— Plus lisible quetest() - Un test = un comportement — Pas de tests multi-scénarios
- Datasets — Pour tester avec différentes valeurs
- Mocks — Pour isoler les dépendances externes
- Architecture tests — Pour les règles d'architecture
- Coverage minimum —
--min=80en CI - Parallel —
--parallelpour la vitesse - TIA —
--tiaen dev pour les tests rapides - Snapshots — Pour les sorties complexes
- Custom expectations — Pour les assertions réutilisables
Cross-références
laravel-testing— Tests Laravel avec Pestlaravel-core— Fondamentaux Laravel
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

