php-testing

SkillAI & models

Use when writing/configuring tests on a non-Laravel PHP project — PHPUnit vs Pest. Do NOT use for Laravel test helpers or quality tooling (php-quality-tooling).

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the php-testing skill

What this skill tells your AI

The instructions your AI receives, as published by fusengine/agents in plugins/php-expert/skills/php-testing/SKILL.md and read by ahel’s review.

Includes a decision matrix for choosing between the two (team preference, not capability), plus templates for phpunit.xml, Pest.php setup, and test doubles (stubs, mocks, fixtures, coverage).

Do NOT use this skill for Laravel test helpers such as RefreshDatabase or HTTP testing — those are covered by laravel-expert's laravel-testing skill. Do NOT use it for static analysis or formatting tooling — that is php-quality-tooling.

PHP Testing

Two frameworks, one engine. Pest is a layer over PHPUnit — both need PHP 8.3+ and share the same runner and assertions underneath.

Agent Workflow (MANDATORY)

Before ANY implementation, spawn 3 agents in parallel, one Agent call each with a name:

  1. fuse-ai-pilot:explore-codebase - Detect existing framework (phpunit.xml vs Pest.php), test layout, PHP version
  2. fuse-ai-pilot:research-expert - Verify latest PHPUnit 12 / Pest 4 docs via Context7/Exa
  3. mcp__context7__query-docs - Check attribute names, test-double API, config schema

After implementation, run fuse-ai-pilot:sniper for validation.


Overview

FrameworkStyleStrength
PHPUnit 12Class-based, xUnitEnterprise baseline, explicit, widest tooling/CI support
Pest 4Closure-based, expressiveModern DX, browser + arch + mutation testing, less boilerplate

Pest 4 runs on PHPUnit's engine, so PHPUnit knowledge transfers directly. The choice is about ergonomics and team preference, not capability.


Critical Rules

  1. PHP 8.3+ required - Both PHPUnit 12 and Pest 4 drop older PHP
  2. Attributes only, no annotations - PHPUnit 12 removed docblock annotations (@test, @dataProvider)
  3. Data providers are public static - Non-static providers no longer work
  4. createStub() is not configurable - Configure expectations only on createMock()
  5. One framework per project - Pest or PHPUnit, not both driving the same suite
  6. Mock at boundaries - Network/DB/clock, never internal implementation detail

Decision Guide

Choosing a framework? (non-Laravel)
├── Enterprise / strict CI / mixed team → PHPUnit 12 (explicit, ubiquitous)
├── Modern DX, greenfield, small team → Pest 4 (concise, expressive)
├── Need browser / architecture / mutation testing out of the box → Pest 4
└── Migrating a large PHPUnit suite → stay PHPUnit, or drift-migrate to Pest

Honest note: PHPUnit is the enterprise/CI baseline; Pest has strong community momentum but that trend is not normative. Either is a correct, well-supported choice — pick per team, not per hype.

→ Full matrix in references/choosing-framework.md


Reference Guide

Concepts

TopicReferenceLoad when
Framework choicereferences/choosing-framework.mdDeciding PHPUnit vs Pest
PHPUnit 12references/phpunit-12.mdWriting PHPUnit tests
Pest 4references/pest-4.mdWriting Pest tests
Annotation migrationreferences/annotations-to-attributes.mdUpgrading pre-12 tests

Templates

TemplateUse Case
references/templates/phpunit-xml.mdphpunit.xml + first TestCase
references/templates/pest-setup.mdPest.php + first Pest tests
references/templates/test-doubles.mdStubs, mocks, fixtures, coverage

Quick Start

PHPUnit 12

use PHPUnit\Framework\TestCase;

final class GreeterTest extends TestCase
{
    public function testGreets(): void
    {
        $this->assertSame('Hi, Al', (new Greeter)->greet('Al'));
    }
}

Pest 4

it('greets', function () {
    expect((new Greeter)->greet('Al'))->toBe('Hi, Al');
});

Best Practices

DO

  • Name tests by behavior (testRejectsExpiredToken), assert one behavior each
  • Use #[DataProvider] with public static providers for table-driven tests
  • Gate CI on an explicit coverage threshold, not a report

DON'T

  • Keep @test / @dataProvider annotations (removed in PHPUnit 12)
  • Configure expectations on createStub() results
  • Mock the class under test — mock its collaborators

Signals

GitHub stars
25
Forks
4
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
php-testing
Source
github.com/fusengine/agents