laravel/browser-kit-testing
Fluent BrowserKit-style testing for Laravel: make HTTP requests, follow routes, fill forms, and assert response content with simple methods like visit, see, and dontSee. Install as a dev dependency and extend Laravel\BrowserKitTesting\TestCase.
composer require laravel/browser-kit-testing --dev
Illuminate\Foundation\Testing\TestCase with Laravel\BrowserKitTesting\TestCase in your Tests/TestCase.php:
use Laravel\BrowserKitTesting\TestCase as BaseTestCase;
public function test_homepage_loads()
{
$this->visit('/')
->see('Welcome');
}
visit(), see(), click(), and type() are chainable for fluent testing.json(), seeJson(), and seeJsonStructure() for API endpoints.actingAs() for testing authenticated routes.Form Submission Testing:
public function test_user_registration()
{
$this->visit('/register')
->type('John Doe', 'name')
->type('john@example.com', 'email')
->check('terms')
->press('Register')
->seePageIs('/dashboard');
}
API Contract Testing:
public function test_create_user_api()
{
$this->json('POST', '/api/users', ['name' => 'Jane'])
->seeJsonStructure(['id', 'name', 'created_at'])
->assertResponseStatus(201);
}
Session/State Testing:
public function test_session_persistence()
{
$this->withSession(['theme' => 'dark'])
->visit('/dashboard')
->see('Dark Theme');
}
Middleware Isolation:
public function test_route_without_middleware()
{
$this->withoutMiddleware()
->visit('/admin')
->see('Unauthenticated Access');
}
$user = User::factory()->create();
$this->actingAs($user)->visit('/profile');
protected function assertFlashMessageContains($message)
{
$this->see($message);
}
laravel/browser-kit-testing alongside laravel/dusk for browser automation when needed.Middleware Leaks:
Forgetting to disable middleware (e.g., auth) can cause tests to fail unexpectedly. Use withoutMiddleware() or the WithoutMiddleware trait.
// ❌ Fails if route requires auth
$this->visit('/admin');
// ✅ Works
$this->withoutMiddleware()->visit('/admin');
Session State Pollution: Shared test state (e.g., session data) can cause flaky tests. Reset state between tests:
public function tearDown(): void
{
$this->withSession([]); // Clear session
parent::tearDown();
}
Dynamic Routes: Named routes with parameters require exact matching:
// ❌ Fails if route expects `user` param
$this->visitRoute('profile');
// ✅ Works
$this->visitRoute('profile', ['user' => 1]);
File Uploads:
Paths in attach() must be absolute or resolved correctly:
// ❌ Fails if path is relative
$this->attach('test.jpg', 'avatar');
// ✅ Works
$this->attach(public_path('test.jpg'), 'avatar');
dump($this->response->getContent()) to debug HTML/JSON responses.APP_DEBUG=true) to log requests in the terminal.DatabaseTransactions if not needed.DatabaseMigrations for large datasets.Custom Matchers: Extend the test case to add domain-specific assertions:
public function seeUserName($name)
{
$this->see('Welcome, '.$name);
}
Hooks for Setup/Teardown:
Override setUp() and tearDown() for shared test logic:
protected function setUp(): void
{
parent::setUp();
$this->actingAs(User::factory()->create());
}
Mocking External Services: Use Laravel's HTTP clients or mocks to isolate tests:
$this->mock(Http::class)->shouldReceive('post')->once();
Parallel Testing:
BrowserKit tests are not inherently parallelizable due to session state. Use pestphp/pest or PHPUnit's --parallel with caution.
```markdown
---
How can I help you explore Laravel packages today?