puntodev/mercadopago-fake
A Laravel/PHP package for faking Mercado Pago in tests. Simulate preferences, payments, webhooks, and API responses without real credentials or network calls, enabling fast, deterministic feature tests and safe local development.
puntodev/mercadopago-fake is a test-only companion to puntodev/mercadopago: it lets you swap the real MercadoPago SDK client for an in-memory fake so feature tests can assert on preference/charge/payment calls without hitting the network. It ships nothing you should ever load in production.
Requirements for 2.0.0 (breaking):
puntodev/mercadopago ^8.0If the project runs PHP 8.4, do not upgrade — stay on the 1.x line, which keeps the older SDK constraint. This is not a "bump and fix a few tests" release; the PHP floor alone rules it out for most current Laravel apps.
Minimal setup:
composer require --dev puntodev/mercadopago-fake
Where to look first:
vendor/puntodev/mercadopago-fake/README.md — the documented fake entry point and assertion helpers.vendor/puntodev/mercadopago-fake/src/ — the fake's public surface and any response-building helpers; this is the source of truth when the README lags.tests/ — the most reliable usage examples, since they run against the same SDK major you just installed.puntodev/mercadopago's 8.0 upgrade notes — the fake's signatures track the real client, so SDK renames land here too.First use case: a feature test that creates a checkout preference through your own service class and asserts the correct payload was sent upstream:
public function test_checkout_creates_preference(): void
{
MercadoPago::fake();
$this->post('/checkout', ['cart' => $cart->id])->assertRedirect();
MercadoPago::assertPreferenceCreated(fn ($payload) => $payload['items'][0]['quantity'] === 2);
}
Start by faking the happy path for one endpoint, then add assertion coverage for the failure branches (rejected card, expired preference) — that's where the fake pays for itself.
Fake per test, not globally. Call the fake in setUp() of the specific test case (or a dedicated MercadoPagoTestCase), not in TestCase.php. Global fakes silently mask accidental real calls in unrelated suites.
Fake before the container resolves the client. The fake works by rebinding/replacing the SDK client in the container. If your service provider, a singleton, or a constructor-injected client is resolved before the fake is installed, you'll be talking to the real HTTP client. Order matters: fake in setUp() before parent::setUp() only if your app's providers don't touch the client at boot; otherwise fake immediately after parent::setUp() and before resolving any service that owns the client.
Arrange → act → assert, mirroring the SDK. Treat the fake as a recorder:
assert* helpers rather than re-implementing payload inspection.Test queued jobs and webhooks end-to-end. The highest-value integration is: dispatch the job/command that talks to MercadoPago, Queue::fake() is not what you want here — let the job run synchronously so the fake records the call. For inbound webhooks, fake the outbound calls your handler makes (e.g. fetching the payment to verify status) and assert the handler's persisted state.
Wrap the SDK in your own gateway class. Bind a small MercadoPagoGateway interface in the container and let the fake sit behind it for SDK-level tests, while other tests use your own hand-rolled fake gateway. This keeps 90% of your suite independent of both the SDK and this package, so a future major bump becomes a two-file change instead of a suite-wide rewrite.
CI matrix discipline. With PHP 8.5 as the floor, add 8.5 to the test matrix before bumping, and pin the package by major (^2.0) so a future 3.0 can't land silently. Run the suite against both 1.x and 2.x only if you actually intend to support both.
Combine with RefreshDatabase and Laravel's Http fakes carefully. If your code path could reach MercadoPago both through the SDK and through a raw HTTP call, fake both layers — otherwise the SDK fake gives false confidence while Http::preventStrayRequests() is the only thing catching leaks.
PHP 8.5 is a hard wall. Composer will simply refuse to install on 8.4. If your composer.json allows puntodev/mercadopago-fake: ^1.0 || ^2.0, a composer update on a mixed-version team can resolve differently per machine. Pin explicitly per branch, or constrain with ^2.0@php:8.5 style platform checks.
SDK 8.0 renames will surface here. The fake mirrors the real client's method names. After upgrading, grep your tests for assertion helpers — a renamed method on the real SDK usually means a renamed assert* helper. Failures of the form "method does not exist" on the fake are almost always this, not a bug in the package.
Container rebinding bugs look like fake bugs. Symptom: assertions report zero recorded calls even though the code clearly ran. Cause is nearly always that the client was resolved and cached (singleton, constructor injection into a long-lived service, or an octane-style boot) before the fake installed. Fix by faking earlier or by flushing the resolved instance.
Assertions on payload shape are brittle across SDK majors. 8.0 may normalize or rename fields in request bodies. Prefer asserting on the fields your domain cares about (amount, quantity, external reference) over full-array equality — full-payload snapshots will break on every minor SDK release.
Don't let the fake leak into production config. It's require-dev; if you see it in composer.lock's non-dev section, a transitive dependency pulled it in. Also ensure any service provider registration is guarded by $this->app->environment('testing') or only registered from your test case.
Reset state between tests. Fakes are typically bound fresh per test, but if you install one in a setUpBeforeClass or a shared trait with static state, recorded calls accumulate and assertions pass for the wrong reason. When in doubt, assert on counts, not just presence.
Dependabot noise is expected. The 2.0.0 line moved to a single weekly grouped Dependabot PR with majors excluded. If you maintain a fork or a vendored copy, mirror that config or you'll get daily PR churn across PHP, Composer, and Actions ecosystems.
Extension points worth knowing: custom response builders/payload factories for reusable fixtures across suites, and the container binding itself — if the shipped fake doesn't cover an SDK call you need, bind your own implementation of the same contract rather than patching vendor/. Fork with a version constraint you control if you need upstream changes.
How can I help you explore Laravel packages today?