Testing components that depend on third-party libraries can be painful. Take Stripe for example — you would need to set up test plans, customers, subscriptions, invoices, and more within Stripe’s test mode. This can get cumbersome quickly.
What if you could test your Stripe integration without ever hitting the Stripe API? By leveraging Laravel facades, you can create a mockable wrapper around any external service.
The approach has three parts:
The wrapper class sets the API key once during initialization and dynamically forwards method calls to the appropriate Stripe classes:
<?php
namespace App\Wrappers;
class StripeWrapper
{
public function __construct($secret)
{
\Stripe\Stripe::setApiKey($secret);
}
public function __call($class, $arguments)
{
$class = "\\Stripe\\$class";
if (! class_exists($class)) {
throw new \Exception("$class does not exist");
}
if (! count($arguments)) {
throw new \Exception("Missing method name");
}
$method = $arguments[0];
if (! method_exists($class, $method)) {
throw new \Exception("$class::$method does not exist");
}
$arguments = count($arguments) > 1
? array_slice($arguments, 1)
: [];
return call_user_func_array([$class, $method], $arguments);
}
}
When you call Stripe::Invoice('upcoming', ['customer' => $stripeId]), the wrapper translates this into \Stripe\Invoice::upcoming(['customer' => $stripeId]).
In your AppServiceProvider, bind the wrapper as a singleton so the API key is set once and reused:
public function register()
{
$this->app->singleton(
\App\Wrappers\StripeWrapper::class,
function ($app) {
return new \App\Wrappers\StripeWrapper(
config('services.stripe.secret')
);
}
);
$this->app->alias(
\App\Wrappers\StripeWrapper::class,
'stripe_wrapper'
);
}
Create a facade that points to the wrapper’s service container alias:
<?php
namespace App\Facades;
use Illuminate\Support\Facades\Facade;
class Stripe extends Facade
{
protected static function getFacadeAccessor()
{
return 'stripe_wrapper';
}
}
As the Laravel documentation states, facades serve as “static proxies” to underlying classes in the service container, providing the benefit of a terse, expressive syntax while maintaining more testability and flexibility than traditional static methods.
use App\Facades\Stripe;
$invoice = Stripe::Invoice('upcoming', ['customer' => $stripeId]);
Now you can mock the entire Stripe integration without making a single API call:
use App\Facades\Stripe;
public function testUpcomingInvoice()
{
Stripe::shouldReceive('Invoice')
->once()
->with('upcoming', ['customer' => 'cus_XXX'])
->andReturn($mockedResponse);
// Run your code that calls Stripe::Invoice(...)
// and assert against the mocked response
}
This pattern works because of how Laravel’s facade system is built. When you call Stripe::shouldReceive(...), Laravel swaps the real singleton with a Mockery instance. Your production code calls the facade the same way, but in tests, the mock intercepts the call and returns whatever you’ve configured — no HTTP requests, no external dependencies.
This wrapper + facade pattern isn’t limited to Stripe. You can apply it to any PHP SDK — Twilio, SendGrid, AWS, you name it. The key benefits are: