Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Testbench Laravel Package

graham-campbell/testbench

Laravel TestBench offers testing functionality for Laravel projects and packages, built on PHPUnit, Mockery, Orchestra Testbench, and Laravel Testbench Core. It supports Laravel 8-13, PHP 7.4-8.5, and PHPUnit 9-11 for streamlined test suites.

View on GitHub
Deep Wiki
Context7

Getting Started

Laravel TestBench is a dev-only dependency that gives you Laravel-aware base test classes for package development. It is not something you install into an application's test suite — if you're testing an app, use Laravel's own Tests\TestCase. If you're publishing a package that registers service providers, config, routes, migrations, views, or commands, this is the scaffolding that removes the boilerplate.

Install it as a dev dependency:

composer require "graham-campbell/testbench:^6.3" --dev

There is no service provider to register and no config file to publish. The install is the setup.

Where to look first. The README points you at AbstractTestCase — take that literally. Then open vendor/graham-campbell/testbench/src/ and vendor/graham-campbell/testbench-core/src/ and read the class list before writing anything. The package is deliberately thin: it's a curated set of abstract test cases and traits layered on top of orchestral/testbench. The trait names in testbench-core tell you what's covered (providers, routes, views/blade, migrations, commands, controllers, jobs, listeners, middleware, helpers, validators) far faster than any docs will.

First use case. You have a package with a service provider that merges config and registers a singleton. Your first test file should extend AbstractPackageTestCase (or its TestBench equivalent), declare the provider, and assert the binding exists:

namespace Acme\Widget\Tests;

use Acme\Widget\WidgetServiceProvider;
use GrahamCampbell\TestBench\AbstractPackageTestCase;

abstract class AbstractTestCase extends AbstractPackageTestCase
{
    protected static function getRequiredServiceProviders(): array
    {
        return [WidgetServiceProvider::class];
    }
}
namespace Acme\Widget\Tests;

use Acme\Widget\WidgetManager;

class ServiceProviderTest extends AbstractTestCase
{
    public function testItBindsTheManager(): void
    {
        $this->assertInstanceOf(WidgetManager::class, $this->app->make(WidgetManager::class));
    }

    public function testItMergesConfig(): void
    {
        $this->assertSame(3, $this->app['config']->get('widget.retries'));
    }
}

That's the 80% case: provider boot/register correctness, config merging, and container bindings — verified without hand-rolling an application bootstrap in every test.

Implementation Patterns

Pick the right base class for the layer under test. Two distinct shapes exist and mixing them up is the most common source of confusion:

  • AbstractPackageTestCase — spins up a minimal application containing only the providers you list via getRequiredServiceProviders(). Use it for provider registration, config merge/publish, container bindings, and anything where isolating your provider matters. Because you control the provider list, a broken third-party provider can't fail your tests.
  • AbstractAppTestCase — boots a fuller application skeleton rooted at getBasePath() (now static as of v6). Use it when you need framework furniture your package assumes exists: routes resolving through the router, views rendering, migrations running against a real connection.

Rule of thumb: start with the package test case. Escalate to the app test case only when a test genuinely needs the wider skeleton.

Keep a single abstract TestCase per package and extend it everywhere. Define provider lists and getBasePath() once, then have every concrete test extend it. This matters because v6.0 made both getBasePath() and getRequiredServiceProviders() static and added return types across the board — one place to fix if signatures shift again.

Mockery is already wired in. The base classes integrate Mockery and count its assertions, so shouldReceive(...)->once() failures surface as real test failures rather than silently passing. Lean on constructor/container mocking for collaborators rather than instantiating heavy dependencies:

public function testItDelegatesToTheClient(): void
{
    $client = $this->mock(HttpClient::class);
    $client->shouldReceive('get')->once()->with('widgets')->andReturn(['id' => 1]);

    $manager = $this->app->make(WidgetManager::class);

    $this->assertSame(1, $manager->fetch()[0]['id']);
}

Use the JSON assertion helper for API-ish contracts. TestBench adds assertInJson for asserting that a JSON string contains expected key/value fragments without fragile whole-payload string comparison — useful for command output and provider-published payloads:

$this->assertInJson('{"status":"ok","count":2}', ['status' => 'ok']);

Use the class-injection assertion for container hygiene. Rather than assertInstanceOf on $this->app->make(...) everywhere, the package's injection assertion documents intent: "the container resolves this class, and it is wired correctly." Reserve plain instanceof checks for cases where you're asserting a specific concrete implementation behind an interface.

Test the artefacts your provider publishes, not just its bindings. Pair provider tests with the route/view/migration traits from the core package. For a provider that calls loadMigrationsFrom, a short test that runs migrations against the TestBench app catches path typos that a binding assertion never will — and those typos are the single most frequent packaging bug in Laravel packages.

Follow Graham Campbell's own packages as reference implementations. The README explicitly says the usage example is "pretty much all of my Laravel packages." Cloning any of them gives you a working phpunit.xml, base test case, and CI matrix that you can adapt in minutes. This is the highest-leverage five minutes you can spend on setup.

Gotchas and Tips

The version matrix is the whole ballgame. The support table is a triple constraint — PHP × Laravel × PHPUnit — and it is not monotonic. TestBench 6.3 covers PHP 7.4–8.5, Laravel 8–13, PHPUnit 9–11. Note that PHPUnit support shrank historically (5.7.1 explicitly walked back PHPUnit 10 support mid-release) and the README states PHPUnit 12 is unsupported due to volatility across its minor releases — while simultaneously recommending "sticking with PHPUnit 12." That sentence is contradictory; treat the composer constraint, not the prose, as authoritative, and run composer why-not phpunit/phpunit 12 when resolution fails. Pin phpunit/phpunit explicitly in require-dev rather than letting it float.

Don't register anything. There is no service provider and no configuration. If you find yourself adding an entry to config/app.php or publishing a config file, you've misread the install — the base classes are consumed by extends only.

Static signature changes are a hard break, not a deprecation. v6.0 converted AbstractAppTestCase::getBasePath() and AbstractPackageTestCase::getRequiredServiceProviders() to static and dropped the $app parameter from the latter. If you're porting tests from v5.x, overrides with non-static signatures will fatal. Same story for return types: v6.0 "added return types in all places," so child overrides must declare compatible return types or PHP throws a signature incompatibility error. When upgrading, grep your test suite for these two method names first.

A dummy encryption key is set for you. This dates back to v3.0.1 and is why encrypt()/Crypt work in TestBench-based tests without you touching .env. If you're seeing "No application encryption key has been specified," you're probably not extending the TestBench base class — check your extends chain before debugging config.

Two packages, one mental model. graham-campbell/testbench is a thin layer over graham-campbell/testbench-core over orchestral/testbench. When a trait or method seems missing, look in testbench-core's src/ before assuming it doesn't exist — the split is historical (v3.0) and the main repo's README will not enumerate everything you actually inherit.

Debugging boot failures. When a test blows up in setUp() with an opaque container or config error, temporarily trim getRequiredServiceProviders() to your own provider only. That isolates whether the failure is your provider or a dependency's — a two-line change that saves far more time than reading stack traces through four layers of framework bootstrapping.

Extension points worth knowing. All base classes are abstract and unopinionated, so the intended customisation path is overriding getRequiredServiceProviders() / getBasePath() and adding your own trait-based helpers in your package's abstract TestCase. There is no plugin system, no config-driven behaviour, and no event hooks — which is a feature: the surface area you can break by upgrading is tiny.

Fit assessment. At 9.3 with zero dependents listed, this is a mature, narrowly-scoped, low-churn package maintained by a prolific package author. It is not a package you adopt for an application's test suite, and it isn't a general-purpose testing toolkit. For Laravel package authors it eliminates a genuinely tedious chunk of boilerplate; for everyone else it's irrelevant. Adopt it if you maintain packages, and budget roughly an hour of reading the core package source before you're productive.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
laravel2580/idehelpers
delatbabel/viewpages
anavel/gettext
muhamadrezaar/highcharts
delatbabel/keylists
frankkessler/salesforce-laravel-oauth2-rest
anavel/foundation
wdmtech/enviricons
eii/laravel-captcha
muka/shape-reader
tdt/json
refinery29/php-cs-fixer-config
hotmeteor/eco-env
mvaliolahi/hydrate
wearede/tbcpay-php
parsnick/steak
thibaud-dauce/mattermost-php
multilinguals/apollo-client
antaresbr/collection
kr0lik/phpstan-rules