Installation
Testing for Laravel is a dev-only package — install it into require-dev. It ships assertions, a base test case, fixtures and Pest expectations. There is no service provider, no config file, no migration and nothing that runs in a production code path:
composer require --dev roundly-consulting/testing-for-laravel --with-all-dependencies--with-all-dependencies (-W) matters in a fresh Laravel app: its skeleton locks a phpunit/phpunit your Pest major may not allow — each Pest minor caps the PHPUnit patch it supports, and Pest 4 needs PHPUnit 12 while Pest 5 needs PHPUnit 13 — so a plain composer require --dev fails on the lock. -W lets Composer move PHPUnit to a supported version. Projects that already run Pest 4 or Pest 5 need no flag: Composer installs the package against the Pest major you have.
Pest 4 or Pest 5
- The package requires pestphp/pest ^4.0|^5.0, so it fits the suite you already run.
- Pest 4 runs on Laravel 12 and 13 (PHPUnit 12).
- Pest 5 is supported since 1.1.0 — pin ^1.1 — and runs on Laravel 13 only: it needs PHPUnit 13 and symfony/process 8, while Laravel 12 needs Symfony 7 and Testbench 10 conflicts with PHPUnit 13. Composer cannot resolve Pest 5 with Laravel 12.
- Every expectation, arch preset and the base test case behave the same on both majors; the package’s CI runs on Laravel 12 + Pest 4, Laravel 13 + Pest 4 and Laravel 13 + Pest 5.
The Pest expectations register automatically through the package’s Pest plugin (extra.pest.plugins), so they exist the moment the package is installed. Suites that disable plugin discovery register them explicitly in tests/Pest.php:
<?php
declare(strict_types=1);
use RoundlyConsulting\Testing\Expectations\Expectations;
uses(Tests\TestCase::class)->in('Arch', 'Feature', 'Unit');
Expectations::register(); // idempotent — a no-op if the Pest plugin already ranBoth paths coexist: Expectations::register() guards on an internal flag, so calling it after the plugin already ran is a no-op. Expectations::registered() reports whether registration has happened.
Bind the Arch directory to your TestCase too, as above. Several checks read the booted application — swappableModelsAreNotFinal, toBeSwappableVia and toBeFakeable among them — and an arch file bound to no TestCase has none; they fail with instructions rather than passing.
Three call styles
Assertions are reachable in three styles. Lead with the Pest expectation — it is the canonical, documented API:
use RoundlyConsulting\Testing\Arch\ArchPresets;
use RoundlyConsulting\Testing\Assert;
// 1. Pest expectation — the canonical API
expect(database_path('migrations'))->toHaveRunnableMigrationOrder();
// 2. Static mirror — for plain PHPUnit test classes
Assert::migrationsRunInDependencyOrder(database_path('migrations'));
// 3. Arch preset — at the top level of a Pest arch file
ArchPresets::strictTypes('App');| Example | Style |
|---|---|
expect($dir)->toHaveRunnableMigrationOrder() | Pest expectation — the canonical API in any Pest suite. |
Assert::migrationsRunInDependencyOrder($dir) | Static mirror — class-based PHPUnit tests. |
ArchPresets::strictTypes($namespace) | Arch preset — the top level of a Pest arch file; it registers its own case. |
Package surface vs app surface
- Works in any Laravel app, Testbench-free: the static Assert, every Pest expectation except the two publish-only guards, and ArchPresets. The package’s own arch test enforces that none of them references Testbench.
- Package-only: PackageTestCase, the LoadsProviderMigrations concern, and the toNotAutoLoadMigrations / toPublishMigrationsTimestamped expectations — they assume a package service provider and Testbench.
Show your open-source love
This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.
More ways to support, including cryptoBy donating, you agree to our donation terms.
Want this built into your product?
We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.