graham-campbell/testbench-core
Laravel TestBench Core by Graham Campbell provides reusable testing functionality for Laravel packages and applications, supporting PHPUnit 9-12 and Laravel 8-13 on PHP 7.4-8.5.
graham-campbell/testbench-core is a dev-only trait library. It ships no service provider and no config, so "installing" it does nothing visible — its value appears the moment you use one of its traits in a test class. Reach for it when you are testing a package-shaped codebase (a service provider plus optional facades) and you want the provider/facade assertions and Mockery lifecycle handled for you instead of hand-rolled in every package.
Install as a dev dependency, pinned to a major:
composer require --dev "graham-campbell/testbench-core:^4.3"
Pin phpunit/phpunit to ^12.0 at the same time. The README explicitly states PHPUnit 13 is unsupported, and v4.3 is the row that covers PHP 7.4–8.5 / PHPUnit 9–12 / Laravel 8–13. Composer will resolve the intersection for you, but a bare ^4.3 next to phpunit:^13 will produce a confusing conflict rather than a clear message.
Where to look first: the src/*Trait.php files. Each is small, and the public test* methods inside them are the specification — there is no separate docs page. Read ServiceProviderTrait and FacadeTrait before anything else; those are the two you will actually use.
First use case — a provider test. Create one dedicated test class per package:
<?php declare(strict_types=1);
namespace Acme\Widget\Tests;
use Acme\Widget\WidgetServiceProvider;
use GrahamCampbell\TestBench\AbstractPackageTestCase;
use GrahamCampbell\TestBenchCore\ServiceProviderTrait;
final class ServiceProviderTest extends AbstractPackageTestCase
{
use ServiceProviderTrait;
protected function getPackageProviders($app): array
{
return [WidgetServiceProvider::class];
}
protected static function getServiceProviderClass(): string
{
return WidgetServiceProvider::class;
}
}
AbstractPackageTestCase comes from the sibling package graham-campbell/testbench; if you already depend on orchestra/testbench directly, extend Orchestra\Testbench\TestCase instead and keep only the trait. Either way the trait does the asserting.
One provider test class, not many. The traits contribute real test* methods. Put them in a single ServiceProviderTest and keep every other test class trait-free, otherwise the same provider assertions run once per test class and your suite count inflates.
Abstract package base case. If you maintain several packages, define one base class in your own src/Testing (or a shared dev package) that only wires up the container providers, and let each package's ServiceProviderTest add the traits:
abstract class PackageTestCase extends AbstractPackageTestCase
{
protected function getPackageProviders($app): array
{
return $this->providers(); // per-package list
}
abstract protected function providers(): array;
}
Facade pattern. FacadeTrait asserts that the facade's accessor string matches a container binding and that resolving the root produces the expected class. Implement the three abstract hooks once; note that getServiceProviderClass() is shared with ServiceProviderTrait, so define it once and use both traits in the same class.
use GrahamCampbell\TestBenchCore\FacadeTrait;
use GrahamCampbell\TestBenchCore\ServiceProviderTrait;
protected static function getFacadeClass(): string { return WidgetFacade::class; }
protected static function getFacadeRoot(): string { return WidgetManager::class; }
protected static function getFacadeAccessor(): string { return 'widget'; }
protected static function getServiceProviderClass(): string { return WidgetServiceProvider::class; }
getFacadeRoot() returns the class of the resolved instance, not the facade class — that is the assertion that catches mis-wired $app->bind() calls.
Mockery. MockeryTrait closes Mockery in tearDown, so you never write Mockery::close() yourself. Compose it into the base class rather than the individual test class, and use Mockery::mock() normally.
Assertion helpers. HelperTrait supplies assertArraySubset, which PHPUnit removed in 9. If you migrated from PHPUnit 8 and only just noticed the method is gone, pulling in this trait is a one-line fix — but verify the strict/loose comparison behaviour matches what the old PHPUnit method did before you rely on it for nested-array assertions.
CI matrix. Treat the compatibility table as your build matrix, not your lockfile: run PHP × Laravel × PHPUnit combinations explicitly (e.g. PHP 8.2/Laravel 10/PHPUnit 10 and PHP 8.4/Laravel 12/PHPUnit 12) so the dev dependency's window is exercised rather than assumed.
Integration tip. This package is the trait layer beneath Graham Campbell's own packages; graham-campbell/testbench adds the abstract test cases, and orchestra/testbench does the app bootstrapping. You can use the traits with plain Orchestra and skip the rest entirely.
v3 → v4 is a breaking signature change. In v4.0, ServiceProviderTrait::getServiceProviderClass() became static and dropped the $app parameter, and FacadeTrait's getFacadeAccessor / getFacadeClass / getFacadeRoot / getServiceProviderClass all became static. A trait abstract method with a mismatched signature fails at class-load time, not with a friendly message. Fix in one pass:
// v3
protected function getServiceProviderClass($app): string
// v4
protected static function getServiceProviderClass(): string
Nothing to publish, nothing to register. If composer require "did nothing", that is correct. Do not go looking for a providers entry or a config/testbench-core.php.
Trait test methods are public and unconditional. They run wherever the trait lands. Combine with --filter / --list-tests when debugging to see exactly which methods a trait added.
Coverage metadata will bite in PHPUnit 10+. Because the trait injects tests into your class, enabling requireCoverageMetadata="true" makes those injected tests fail unless the class carries #[CoversClass(...)] (or #[CoversNothing]). Add the attribute on the provider test class rather than disabling the setting globally.
tearDown ordering. If you override tearDown in a class using MockeryTrait, call parent::tearDown() — that is what closes Mockery. Calling Mockery::close() yourself afterwards is redundant, and asserting expectations after the parent call will not work.
Facade root vs. facade class. Returning WidgetFacade::class from getFacadeRoot() is the most common copy-paste error and produces a mismatched-class assertion that reads like a binding failure.
Pin the dev dependency to a major (^4.3, not *). This is a small, single-maintainer package; it is dev-only so the runtime risk is zero, but an unpinned dev dependency can silently move you across a Laravel/PHPUnit window.
Don't apply these traits to application feature tests. They are shaped for a package's public surface. In a monolith with conventional feature tests they add maintenance noise and no coverage; the honest answer there is to skip the dependency.
Extension points. Everything is a trait with protected static abstract hooks — the intended extension is composition: build your own InteractsWithWidgets trait on top of MockeryTrait, and keep the package traits confined to the provider/facade test class.
How can I help you explore Laravel packages today?