dees040/festing
Festing is a Laravel package that provides handy testing helpers and assertions to speed up writing reliable unit and feature tests. It integrates smoothly with PHPUnit and Laravel's built-in testing tools, keeping your suite clean and expressive.
Install it as a dev-only dependency — it has no runtime role:
composer require dees040/festing --dev
Why it exists. Laravel's Illuminate\Foundation\Testing\TestCase rebuilds the entire application in setUp() for every test method: every service provider re-registers, every container binding is re-resolved, every facade root is re-created. For a suite of a few hundred tests that boot cost dominates wall-clock time, especially on CI. Festing's premise is that most of your tests don't actually need a pristine container — so it lets you reuse a booted application across tests instead of paying for it each time.
Where to look first. In the package src/ directory, find the trait or base test case it ships and read it end-to-end — it is small. Then read the README's usage section for the exact class/trait name and whether there is a config file to publish. Because the package is tiny, the source is the documentation.
First use case. Pick your slowest, most isolated test class — a pure unit test that extends the framework TestCase but only resolves one or two services and never touches the HTTP kernel or database. Point it at Festing's base class, then compare:
php artisan test --filter=YourServiceTest # before
php artisan test --filter=YourServiceTest # after
If a single isolated class gets meaningfully faster, the concept works for you and you can roll it out incrementally. If it doesn't, stop there — don't refactor the whole suite on faith in the "100%" claim.
Adopt via a second base class, not a global swap. Keep your stock Tests\TestCase untouched and add an opt-in sibling:
// tests/TestCase.php (stock — leave alone)
abstract class TestCase extends BaseTestCase
{
use CreatesApplication;
}
// tests/FastTestCase.php (opt-in)
abstract class FastTestCase extends \Dees040\Festing\...\TestCase
{
// whatever Festing's base requires, per README
}
Then migrate test classes one at a time by changing extends TestCase to extends FastTestCase. This is the single most important pattern: it gives you a safe fallback and makes the migration revertable per class.
Classify tests by isolation need. Festing is a good fit for:
It is a poor fit for:
Combine with RefreshDatabase, don't replace it. Application reuse and database state are orthogonal. Festing reuses the container; RefreshDatabase still wraps each test in a transaction. Using one does not imply the other is safe.
Run the suite in a single process. If the app is booted once per process and reused, the first test pays full cost and the rest are near-free. That means php artisan test over the whole suite, not one-off vendor/bin/phpunit tests/SomeFile.php invocations, which reset the process and waste the benefit.
Parallel testing, cautiously. With php artisan test --parallel, each worker is its own process and gets its own cached app, so the isolation model still holds — but verify ordering-sensitive tests first. The risk profile of parallel + reused app is multiplicative.
Reset what you touch in tearDown. If a test mutates something on the shared app, restore it explicitly:
protected function tearDown(): void
{
\Illuminate\Support\Facades\Facade::clearResolvedInstances();
\Mockery::close();
parent::tearDown();
}
State bleeding is the entire risk. Reusing an app means singletons, static properties, resolved facade instances, and config()->set() mutations survive into the next test. A test that passes in isolation and fails in the suite is almost always leakage — bisect with --filter, then grep the preceding test for singleton, instance, config()->set, or ->bind followed by no cleanup.
Facade mocks are the sharpest edge. SomeFacade::shouldReceive(...) swaps the facade's resolved instance in the container. Without an app refresh, that mock can persist into later tests, producing "already mocked" errors or — worse — a later test silently asserting against a stale mock. Always Facade::clearResolvedInstances() in tearDown.
Event/queue/bus/schedule fakes stick too. Event::fake() installs a fake dispatcher in the container. If your app is shared, the next test inherits it and listeners never fire. Assert and reset.
Config changes for a single test. Prefer phpunit.xml env vars over config()->set() at runtime; if you must mutate config, snapshot and restore it in tearDown. Config is the second-most-common source of cross-test contamination after facades.
Migrations run once per process with in-memory SQLite + a reused app. That's a speed win, but it also means test ordering can matter more than you'd expect — a test that assumes a clean schema may see rows from an earlier test if transactions aren't in play.
Don't use it for package development. If you're testing a package's service provider, boot order and deferred provider resolution are the subject under test; a cached app invalidates the whole exercise.
Benchmark honestly. "More than 100%" means "roughly 2x on the author's suite." Your gain depends on how heavy your providers are and how many tests share a process. Measure before rolling out; keep a stock-base control class so you can re-measure later.
Maintenance reality. 36 stars, low activity, and a single-purpose scope. Pin an exact version in composer.json, read the source before upgrading Laravel majors, and be prepared to vendor the trait into tests/ — the mechanism is a few dozen lines, and owning it removes a supply-chain and a Laravel-version-compatibility risk in one move.
How can I help you explore Laravel packages today?