alt-three/testbench
A lightweight testing harness for Laravel packages, providing a bootstrapped application instance with service providers, configuration, and helpers so you can write fast, reliable package tests without a full application skeleton.
AltThree/TestBench is a small collection of PHPUnit traits that you use inside your application's base tests/TestCase.php. It is not a testing framework of its own; it assumes a standard Laravel test suite and only adds helper methods on top.
Check compatibility before installing. The package is archived and its last release was August 2020, so it was written against the Laravel 5.x/6.x and PHPUnit 7/8 era. On Laravel 9+ you will almost certainly hit a Composer resolution failure on phpunit/phpunit or illuminate/support constraints. If you are evaluating it for a legacy app, pin it explicitly:
composer require --dev alt-three/testbench:^1.0
Do not add it to a new project — see Gotchas below.
Read src/ first. The whole package is a handful of trait files, one per file, each with a small number of public methods. There is no service provider, no config file, and effectively no runtime surface beyond the traits themselves. Open each trait and read the method bodies — you can absorb the entire package in a few minutes, and that is the most reliable documentation you will get, since the README is thin and the repo is frozen.
Wire it into your base test case. Traits are consumed by composition, typically once, in the base class every test extends:
namespace Tests;
use AltThree\TestBench\SomeTrait; // whichever trait you need
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
abstract class TestCase extends BaseTestCase
{
use CreatesApplication;
use SomeTrait;
protected function setUp(): void
{
parent::setUp();
// trait initialization, if the trait exposes a hook
}
}
First use case. Pick the single trait whose helpers map to something you already do by hand — most commonly the mocking/spy helpers around Laravel's container-bound services (events, mailer, bus, queue). Write one test that calls the trait helper and asserts on the resulting Mockery expectation, then delete your hand-rolled equivalent. That one-for-one swap is the entire value proposition of the package.
Compose at the base class, use at the leaf. Add traits only to Tests\TestCase, not to individual test classes. Because these are plain PHPUnit traits, a second use of the same trait in a child class silently shadows the parent's copy and you lose any setUp/tearDown bootstrapping the trait performs. One import site keeps behaviour predictable.
Pair with Mockery, don't replace it. The helpers are thin wrappers that resolve a binding from the container and register expectations on it. The idiomatic flow is:
public function testOrderIsDispatched(): void
{
$this->expectsEvents(OrderPlaced::class); // or the trait's equivalent helper
$this->post('/orders', ['sku' => 'ABC'])->assertStatus(201);
}
Keep your existing Mockery::close() in tearDown() (Laravel's base class already does this) and let the trait handle only the container wiring.
Use it to replace withoutEvents() / facade swaps. If you currently do Event::fake() or Bus::fake() in every test that touches a service, the traits are a cleaner place to centralise "fake this, assert it fired, restore it" so individual tests stay short. This is the pattern most worth copying.
Adopt as a reference implementation rather than a dependency. Given the archive status, the highest-leverage pattern is: read the trait, copy the 20–40 lines into your own tests/Concerns/ directory, and own it. You get the same ergonomics with no dead dependency and full control when Laravel's testing APIs shift.
Legacy-suite migration path. If you are modernising an app that already depends on the package, replace each trait with the corresponding Illuminate\Foundation\Testing\* capability (RefreshDatabase, WithoutMiddleware, Event::fake(), Http::fake(), Queue::fake()) one at a time, running the suite after each swap. Do not attempt a single big-bang removal; trait method collisions make failures hard to attribute.
setUp()/tearDown() signatures are void-typed and final in places; a trait declaring untyped versions will fatal with "declaration must be compatible". Expect to edit the trait or skip it entirely.TestCase (or Laravel's own TestCase) already defines a method with the same name, PHP resolves by precedence rules you will not enjoy debugging. Run php -r reflection over the base class, or simply grep the trait for method names before importing.instance() a fake into the container can survive into later tests if the app is not rebuilt per test. Confirm CreatesApplication is in play and that refreshApplication() runs; if a fake "sticks", that is almost always the cause.parent::setUp(). If the trait stubs something the framework needs during boot (config, exception handler), calling it before parent::setUp() will produce confusing "class not found" or "facade root not set" errors. Default to initialising after parent::setUp() unless the trait's source clearly says otherwise.phpunit --filter=YourTest --debug to see exactly where a trait-induced failure lands, and --stop-on-failure when a stub leaks. When a mock expectation reports "no matching handler", the trait probably registered it on a different container binding than the one your code resolves — check app()->bound() for both keys.TestCase (calling parent:: where useful) or fork the file. Forking is the recommended path at this point in the package's life.How can I help you explore Laravel packages today?