Testing
This page is about testing your application. It opens with how the package tests itself, because that is the evidence behind everything below — but those are the package's own commands, run in a checkout of the package. Nothing here is something you run in your app.
How the package proves itself
Not something to copy into your project. It is here so you can see what "tested" means before you decide how much of it you need to repeat:
composer test # unit + feature + cross-engine (Postgres + MySQL)
composer qa # style, static analysis, type + line coverage
The Unit and Feature suites run on fast in-memory SQLite; two further suites re-run against real PostgreSQL and MySQL 8.4 so engine-specific behavior is proven on the databases Laravel Cloud runs. That matters to you indirectly: it is why a query this package issues behaves the same on your deployment as it does here.
Testing your own billing logic
You do not need to reach Stripe to test the code you build on this package. Two seams make it straightforward.
Assert on the neutral domain events. Every webhook the package processes is translated into a
provider-neutral event (SubscriptionStateChanged, AddonPurchased, PaymentFailed, InvoiceFinalized,
MandateRevoked, …) and dispatched through Laravel's own dispatcher, so Event::fake() and
Event::assertDispatched() work exactly as they do for your app's events:
use Illuminate\Support\Facades\Event;
use Pushery\Billing\Events\SubscriptionStateChanged;
Event::fake([SubscriptionStateChanged::class]);
// … deliver a webhook, or run a reconcile …
Event::assertDispatched(SubscriptionStateChanged::class,
fn (SubscriptionStateChanged $e) => $e->tierKey === 'pro');
Fake the outbound actions. The provider-mutating seams are contracts, so bind a fake to keep a test off the network and assert on what your code asked for:
use Pushery\Billing\Contracts\SubscriptionActions;
$actions = Mockery::spy(SubscriptionActions::class);
$this->app->instance(SubscriptionActions::class, $actions);
// … exercise the code that swaps a plan …
$actions->shouldHaveReceived('swap')->with($user, 'pro', true);
The same applies to Checkout (open a subscription) and OneTimeCharge (buy an add-on). No card data, no
live call, no webhook secret needed.
Or use the recording fake. For the common case, Billing::fake() binds a recording fake to all three
seams at once and gives you ready-made assertions — the same shape as Bus::fake():
use Pushery\Billing\Facades\Billing;
Billing::fake();
// … exercise the code that subscribes the user and buys an add-on …
Billing::assertSubscribeStarted($user, 'pro');
Billing::assertSwapped($user, 'premium');
Billing::assertNothingCharged();
To assert the coupon or the declaration reference too, use the value-carrying pair. They are separate methods rather than optional parameters on the two above, because null has to mean something definite: it asserts that no coupon (or no reference) was passed, which is the case worth proving if you validate a code on your own order form before checkout.
Billing::assertSubscribeStartedWithCoupon($user, 'pro', 'WELCOME10');
Billing::assertSubscribeStartedWithCoupon($user, 'pro', null); // and no coupon reached the seam
Billing::assertPurchasedWithDeclaration($user, 'extra-seat', 'DECL-42');
This matters if you show a discount before the buyer commits. The package deliberately does not fail a checkout on an unknown coupon code — the provider decides what a code is worth, and refusing here would turn a typo into a lost sale. On your own order form that is the wrong default, because a discount displayed and never granted is a problem you own. Assert that the code you validated is the one that reached the seam.