NewWe open-sourced 50+ Laravel packages
Custom AI apps, agents and automation — Roundly ConsultingRoundly
All packages

In your application’s tests, swap the client into a recording fake with Plausible::fake() — no real HTTP. It returns a PlausibleFake, a subtype of PlausibleManager, and installs it behind the facade and in the container, so an injected PlausibleManager, plausible(), the Trackable trait and the plausible.track middleware are all recorded. Then assert on what was captured or queried — on the facade or on the returned handle:

use RoundlyConsulting\Money\Money;
use RoundlyConsulting\Plausible\Facades\Plausible;
use RoundlyConsulting\Plausible\Requests\Events\CaptureEventRequest;

it('tracks the purchase', function () {
    Plausible::fake();

    $this->post('/checkout', [/* ... */]);

    Plausible::assertEventCaptured(
        fn (CaptureEventRequest $event) => $event->name === 'purchase'
            && $event->revenue?->amount->equals(Money::ofMajor('49', 'EUR')) === true,
    );
    Plausible::assertEventCapturedCount(1);
});

it('reads the dashboard numbers', function () {
    Plausible::fake();

    $this->get('/admin/dashboard')->assertOk();

    Plausible::assertQueried(fn ($request, string $type) => $type === 'aggregate');
});

Provisioning is recorded too. Site create and delete and goal delete send nothing and answer with canned typed results:

it('provisions analytics for a new tenant', function () {
    Plausible::fake();

    $this->post('/tenants', ['domain' => 'new-tenant.com']);

    Plausible::assertSiteCreated('new-tenant.com');
    Plausible::assertGoalCreated('new-tenant.com');
    Plausible::assertSharedLinkCreated('new-tenant.com');
    Plausible::assertNothingCaptured();
});

it('offboards a tenant', function () {
    $fake = Plausible::fake();   // PlausibleFake — asserts work on the handle too

    $this->delete('/tenants/old-tenant.com');

    $fake->assertSiteDeleted('old-tenant.com');
    $fake->assertGoalDeleted('old-tenant.com', 42);
    $fake->assertSiteNotUpdated();
});

Assertions

AssertionPasses when
assertEventCaptured(?callable)Any event was captured, or one matches the callback.
assertEventNotCaptured(?callable)No event was captured, or none matches the callback.
assertNothingCaptured()No event was captured at all.
assertEventCapturedCount(int)Exactly that many events were captured.
assertQueried(?callable)Any stats query ran, or one matches fn ($request, string $type).
assertNothingQueried()No stats query ran.
assertSiteCreated(?string)Any site, or that domain, was created.
assertSiteNotCreated(?string)No site, or not that domain, was created.
assertSiteUpdated(string)The site with that current domain was updated.
assertSiteNotUpdated(?string)No site, or not that one, was updated.
assertSiteDeleted(string)That site was deleted.
assertSiteNotDeleted(?string)No site, or not that one, was deleted.
assertGoalCreated(?string)Any goal, or a goal for that site, was created.
assertGoalNotCreated(?string)No goal, or none for that site, was created.
assertGoalDeleted(string, int|string)That goal was deleted from that site.
assertGoalNotDeleted(?string, int|string|null)No goal matching the given site and id — both optional — was deleted.
assertSharedLinkCreated(?string)Any shared link, or one for that site, was created.
assertSharedLinkNotCreated(?string)No shared link, or none for that site, was created.

Every positive assertion has a negative twin:

Plausible::assertEventNotCaptured(fn (CaptureEventRequest $event) => $event->name === 'refund');
Plausible::assertNothingCaptured();
Plausible::assertNothingQueried();
Plausible::assertSiteNotCreated('staging.my-site.com');
Plausible::assertSiteNotDeleted();          // no site deleted at all
Plausible::assertSiteNotUpdated('my-site.com');
Plausible::assertGoalNotCreated();
Plausible::assertGoalNotDeleted('my-site.com');   // site and goal id are both optional
Plausible::assertSharedLinkNotCreated();

How the fake behaves

  • It records only — nothing reaches Plausible. HTTP to your configured url is faked (test values fill in an empty url or key) and every call gets an empty results payload, so stats reads return zero metrics and no rows.
  • captureQueued() records the event instead of dispatching a job, so no queue worker is needed — but, like the real client, it still needs a visitor (setVisitor() or fromRequest()), so a test can’t pass for code that throws in production.
  • The middleware and the Trackable trait resolve the client through plausible(), so their captures are recorded as well.
  • assertQueried() callbacks receive the request object and its type: realtime, aggregate, timeseries, breakdown or query.

Asserting on the queue

Without the fake, assert queued captures with Laravel’s Queue::fake() — CaptureEventJob exposes the request, userAgent and ipAddress:

use Illuminate\Support\Facades\Queue;
use RoundlyConsulting\Plausible\Jobs\CaptureEventJob;

it('queues an Order Placed event', function () {
    config()->set('plausible.site', 'my-site.com');
    Queue::fake();

    Order::create(['total' => 99]);

    Queue::assertPushed(
        CaptureEventJob::class,
        fn (CaptureEventJob $job) => $job->request->name === 'Order Placed'
            && $job->request->site === 'my-site.com',
    );
});

Run the package’s own test suite with:

composer test

Show your open-source love

This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.

More ways to support, including crypto

By donating, you agree to our donation terms.

Want this built into your product?

We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.