graham-campbell/testbench
Laravel TestBench adds testing helpers for Laravel packages and apps, built on PHPUnit, Mockery, and Orchestral Testbench. Supports Laravel 8–13 and PHP 7.4–8.5, providing a solid base for fast, reliable package tests.
Installation:
composer require --dev graham-campbell/testbench:^6.3
No additional configuration is required.
First Test Case:
Extend GrahamCampbell\TestBench\AbstractPackageTestCase (for packages) or GrahamCampbell\TestBench\AbstractAppTestCase (for applications) in your test class:
use GrahamCampbell\TestBench\AbstractPackageTestCase;
class ExampleTest extends AbstractPackageTestCase
{
protected function getPackageProviders($app)
{
return ['Vendor\\Package\\ServiceProvider'];
}
}
Run Tests:
phpunit
AbstractPackageTestCase: For testing Laravel packages (bootstraps a fresh Laravel container).AbstractAppTestCase: For testing Laravel applications (uses your existing config/app.php).getPackageProviders(): Define required service providers for package tests.getBasePath(): Override to specify custom paths (e.g., for monorepos).getPackageProviders() to declare dependencies:
protected function getPackageProviders($app)
{
return [
'Vendor\\Auth\\AuthServiceProvider',
'Vendor\\Database\\DatabaseServiceProvider',
];
}
$this->mock(\Vendor\Contracts\Service::class, function ($mock) {
$mock->shouldReceive('doSomething')->andReturn(true);
});
.env.testing (or .env) by default.getEnvironmentSetUp() to modify the app:
protected function getEnvironmentSetUp($app)
{
$app['config']->set('app.debug', false);
}
get(), post()) directly:
$response = $this->get('/api/users');
$response->assertStatus(200);
RefreshDatabase):
use Illuminate\Foundation\Testing\RefreshDatabase;
class UserTest extends AbstractAppTestCase
{
use RefreshDatabase;
}
$this->assertTrue($this->app->bound('service'));
$this->assertEquals('expected', $this->app->make('service')->doSomething());
$this->assertEquals(1, count($this->app['router']->getMiddleware()));
$this->artisan('command:name')
->expectsQuestion('confirm', 'yes')
->assertExitCode(0);
Static Methods in v6+:
getBasePath() and getRequiredServiceProviders() are now static. Avoid passing $app as an argument (deprecated in v6.0+).protected static function getBasePath(): string
{
return __DIR__ . '/../vendor/vendor-package';
}
PHPUnit Version Conflicts:
phpunit.xml aligns:
<phpunit bootstrap="vendor/autoload.php">
<php>
<ini name="error_reporting" value="-1" />
</php>
</phpunit>
Mockery Assertions:
shouldReceive() must match the exact method signature. Use ->any() for dynamic calls:
$mock->shouldReceive('handleRequest')->withAnyArgs()->andReturn(true);
Database Transactions:
setUp()/tearDown():
public function setUp(): void
{
parent::setUp();
DB::table('users')->insert([...]);
}
$this->app->dump();
getPackageProviders() to debug loading:
$this->app->make('log')->info('Providers:', $this->getPackageProviders($this->app));
createApplication() in AbstractAppTestCase to avoid global state pollution:
public function createApplication()
{
$app = require __DIR__.'/../../bootstrap/app.php';
$app->make(Kernel::class)->bootstrap();
return $app;
}
Custom Fixtures:
Override getFixturePath() to load custom database seeds:
protected function getFixturePath(): string
{
return __DIR__ . '/fixtures';
}
Test Traits: Reuse logic across tests with traits:
trait AssertsJson
{
protected function assertInJson(array $data, $response)
{
$this->assertTrue($response->json()->has($data));
}
}
Parallel Testing:
Use PHPUnit’s --parallel flag with TestBench. Ensure getBasePath() is static to avoid conflicts.
.env by default. Use getEnvironmentSetUp() to inject vars:
protected function getEnvironmentSetUp($app)
{
putenv('APP_ENV=testing');
$app['config']->set('database.default', 'sqlite_testing');
}
getEnvironmentSetUp():
$app['config']->set('app.key', 'base64:...');
How can I help you explore Laravel packages today?