Pest Control

SkillFiles & storage

Keeps your agent writing PHPUnit tests instead of Pest and removes any Pest code from the project.

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 Pest Control skill

About this capability

Enforces PHPUnit-only testing in this project. Activates when writing tests, reviewing test files, or when any Pest syntax appears (it(), test(), describe(), uses(), expect() chains, beforeEach/afterEach hooks). Scans for and eliminates all Pest references from code, config, and documentation.

What this skill tells your AI

The instructions your AI receives, as published by bottelet/daybydaycrm in .claude/skills/pest-control/SKILL.md and read by ahel’s review.

Rule 0 — Hard Stop

Pest is NOT installed in this project and must never be used.

This project uses PHPUnit 12+ exclusively.

Never write, suggest, or accept:

  • it('description', fn () => ...)
  • test('description', fn () => ...)
  • describe('group', fn () => ...)
  • uses(SomeClass::class)
  • expect($value)->toBe(...)
  • beforeEach(fn () => ...)
  • afterEach(fn () => ...)
  • pest() configuration

Rule 1 — Correct Test Class Pattern

Every test MUST be a class extending one of the three base classes:

// Company panel tests
class FooTest extends AbstractCompanyPanelTestCase
{
    #[Test]
    public function it_does_something(): void
    {
        // ...
    }
}

// Admin panel tests
class BarTest extends AbstractAdminPanelTestCase
{
    #[Test]
    public function it_does_something(): void
    {
        // ...
    }
}

// Pure unit tests (no DB, no framework boot)
class BazTest extends AbstractTestCase
{
    #[Test]
    public function it_does_something(): void
    {
        // ...
    }
}

Base class locations: Modules/Core/Tests/


Rule 2 — Attribute Syntax

Use PHP 8.1+ attributes for test metadata:

use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\Attributes\CoversClass;

#[Test]
public function it_creates_an_invoice(): void {}

#[Test]
#[DataProvider('invoiceDataProvider')]
public function it_validates_invoice_fields(array $data, string $error): void {}

Never use /** @test */ docblock annotations — use #[Test] attributes.


Rule 3 — Assertion Style

Use PHPUnit assertions, not Pest chains:

// Correct
$this->assertSame('expected', $actual);
$this->assertDatabaseHas('invoices', ['status' => 'paid']);
$this->assertCount(3, $results);

// Wrong — Pest chain
expect($actual)->toBe('expected');
expect($results)->toHaveCount(3);

Rule 4 — Livewire Testing

Filament/Livewire tests use the Livewire facade directly:

use Livewire\Livewire;

Livewire::actingAs($this->user)
    ->test(ListInvoices::class, ['tenant' => 'ivplv2'])
    ->assertSuccessful();

Or the base class helper:

$this->testLivewire(ListInvoices::class)->assertSuccessful();

Rule 5 — File Placement

Modules/<Name>/Tests/Unit/    ← AbstractTestCase, no DB
Modules/<Name>/Tests/Feature/ ← AbstractCompanyPanelTestCase or AbstractAdminPanelTestCase

PHPUnit discovers tests via phpunit.xml:

<testsuite name="Unit">   <directory>Modules/*/Tests/Unit</directory>   </testsuite>
<testsuite name="Feature"><directory>Modules/*/Tests/Feature</directory></testsuite>

Rule 6 — Pest Elimination Checklist

When asked to eliminate Pest from a codebase, check and fix all of the following:

composer.json

  • Remove "pestphp/pest-plugin": true from config.allow-plugins
  • Remove any pestphp/pest* entries from require-dev

Test files

  • Convert it('...', fn () => ...) → class method with #[Test] attribute
  • Convert test('...', fn () => ...) → class method with #[Test] attribute
  • Remove all uses(...) declarations
  • Replace expect(...)->toBe(...) chains with $this->assertSame(...)
  • Replace beforeEachsetUp(), afterEachtearDown()
  • Remove describe() wrappers; flatten into separate methods or classes

Config / tooling

  • Delete pest.php or tests/Pest.php if present
  • Remove any --pest flag from CI workflow commands
  • Update .gitignore comments: # PHPUnit / Pest# PHPUnit
  • Update Makefile comments that mention Pest

Documentation

  • Update CLAUDE.md testing section to state PHPUnit-only
  • Update any README or CONTRIBUTING docs that mention Pest

Rule 7 — Conversion Reference

PestPHPUnit equivalent
it('desc', fn() => ...)#[Test] public function it_desc(): void
test('desc', fn() => ...)#[Test] public function test_desc(): void
expect($x)->toBe($y)$this->assertSame($y, $x)
expect($x)->toEqual($y)$this->assertEquals($y, $x)
expect($x)->toBeTrue()$this->assertTrue($x)
expect($x)->toBeFalse()$this->assertFalse($x)
expect($x)->toBeNull()$this->assertNull($x)
expect($x)->toBeEmpty()$this->assertEmpty($x)
expect($x)->toHaveCount(n)$this->assertCount(n, $x)
expect($x)->toContain($y)$this->assertContains($y, $x)
expect($x)->toMatchArray([...])$this->assertEquals([...], $x)
expect($x)->toBeInstanceOf(Cls::class)$this->assertInstanceOf(Cls::class, $x)
beforeEach(fn() => ...)protected function setUp(): void
afterEach(fn() => ...)protected function tearDown(): void
uses(RefreshDatabase::class)use RefreshDatabase; inside the class
dataset(...)public static function provider(): array + #[DataProvider('provider')]

Signals

GitHub stars
2k
Forks
767
Last commit
Aug 2026
Advanced
Catalog kind
skill
Gateway key
pest-control
Source
github.com/bottelet/daybydaycrm