SuperAPI AI Directory

php-testing

Testing & quality

View repository

Updated 10-15 21:48

About this skill

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.4+ 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 13 / Pest 5 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

Framework Style Strength
PHPUnit 13 Class-based, xUnit Enterprise baseline, explicit, widest tooling/CI support
Pest 5 Closure-based, expressive Modern DX, browser + arch + mutation testing, less boilerplate

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


Critical Rules

  1. PHP 8.4+ required - Both PHPUnit 13 and Pest 5 drop PHP 8.3 (stay on PHPUnit 12 / Pest 4 for 8.3)
  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(); any() is hard-deprecated in PHPUnit 13 — use a stub or a real count (once(), exactly(n))
  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 13 (explicit, ubiquitous)
├── Modern DX, greenfield, small team → Pest 5 (concise, expressive)
├── Need browser / architecture / mutation testing out of the box → Pest 5
└── 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

Topic Reference Load when
Framework choice references/choosing-framework.md Deciding PHPUnit vs Pest
PHPUnit 13 references/phpunit-12.md Writing PHPUnit tests
Pest 5 references/pest-4.md Writing Pest tests
Annotation migration references/annotations-to-attributes.md Upgrading pre-12 tests

Templates

Template Use Case
references/templates/phpunit-xml.md phpunit.xml + first TestCase
references/templates/pest-setup.md Pest.php + first Pest tests
references/templates/test-doubles.md Stubs, mocks, fixtures, coverage

Quick Start

PHPUnit 13

use PHPUnit\Framework\TestCase;

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

Pest 5

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