protonemedia/laravel-dusk-fakes
Laravel Dusk Fakes lets you replace real Dusk browser sessions with fakes in your tests. It offers a fluent API to simulate browser interactions and assert Dusk calls, so you can test Dusk-dependent code quickly without launching a real browser.
Add the package as a dev dependency and publish its config:
composer require protonemedia/laravel-dusk-fakes --dev
php artisan vendor:publish --tag=dusk-fakes-config
The package bridges a fundamental Dusk problem: your PHPUnit process and the application under test are two different processes, so a plain Mail::fake() in setUp() never reaches the app the browser is driving. This package persists fake registrations and recorded interactions to a shared store (typically under storage/framework/) so both sides see the same state.
Where to look first, in order:
config/dusk-fakes.php — enable flag, storage path, and which fakes are supported.DuskFakes trait (or facade, depending on version) in vendor/protonemedia/laravel-dusk-fakes/src — this is the real API surface: the fake* toggles and assert* helpers.DuskTestCase — this is where the trait belongs so every browser test can opt in.A first use case that pays for itself immediately: a registration flow where the controller sends a welcome email and dispatches a job. Fake Mail and Queue at the top of the test, drive the browser through the form, then assert the mail was "sent" to the right address and the job was queued — with no SMTP server and no queue worker involved:
use ProtoneMedia\LaravelDuskFakes\DuskFakes;
class RegistrationTest extends DuskTestCase
{
use DuskFakes;
public function test_new_user_is_welcomed()
{
$this->fakeMail();
$this->fakeQueue();
$this->browse(function (Browser $browser) {
$browser->visit('/register')
->type('email', '[email protected]')
->press('Register')
->assertPathIs('/dashboard');
});
$this->assertMailSent(fn ($mail) => $mail->hasTo('[email protected]'));
$this->assertQueued(SendWelcomeSequence::class);
}
}
Default to faking, opt out per test. Put the common fakes in DuskTestCase::setUp() (Mail, Notifications, HTTP) and only add Queue/Event/Bus fakes in tests that care. This keeps every browser test hermetic by default — no accidental emails from CI, no calls to Stripe or a mailing list.
Use HTTP fakes to make third-party flows deterministic. If a page calls an external API (payment provider, address lookup, feature flag service), fake the HTTP client and return a fixture. The same fake lets you assert the request payload the browser-driven app produced. This is the single biggest reliability win for Dusk suites.
Mirror Laravel's own fake assertions. The package intentionally exposes the same shape (assertSent, assertNothingSent, assertQueued, assertDispatched, and so on) so muscle memory from feature tests carries over. When porting an existing feature test into a Dusk test, the assertion block usually survives with a one-line change to the fake setup.
Combine with browser macros. Register a fakeCheckout() macro on Browser that wires the fakes and stubs the expected HTTP responses, then call it from every checkout test. This keeps per-test setup to a single line and centralizes fixture payloads.
Reset between tests. Call the package's reset/clear method in tearDown() (or re-fake in setUp()). Recorded calls accumulate in the shared store; without cleanup you'll get false positives from a previous test's mail or queued job.
Negative tests are cheap. assertNothingSent() after a failed validation flow is a fast, high-value test that catches accidental mail sends on error paths — a class of bug that plain feature tests often miss because the controller returns before the fake is inspected.
Keep the package out of non-test environments. The service provider swaps container bindings, so guard it with APP_ENV=testing (or the package's enabled config bound to an env var). Never let this ship to staging or production.
dusk --parallel, separate processes may write to the same state file. Either run fakes-using tests serially or verify the installed version namespaces its state per test. Check this before scaling your suite.QUEUE_CONNECTION=sync, or assert the browser reflects the eventual state.composer.json and read the trait source in vendor/ rather than relying on blog posts — the source is short and is the most accurate documentation you'll get.dd() calls in a controller running in a separate process.How can I help you explore Laravel packages today?