Why “it works in the builder” breaks in production
AI app builders can generate a convincing prototype in minutes, but production failures rarely come from the UI looking wrong. They come from mismatched assumptions across boundaries: a Stripe webhook payload that changes shape, a Supabase Edge Function that returns a subtly different error code than the frontend expects, or a React state update that accidentally makes a payment flow non-idempotent. The gap is not “more unit tests.” It’s a missing harness that can verify contracts across systems before you ship.
If your stack is React + Supabase + Tailwind with real integrations, this is where a contract-test harness earns its keep. The goal is simple: validate the exact request/response and event shapes that tie together Supabase Edge Functions, Stripe webhooks, and frontend state transitions—so the prototype doesn’t drift away from reality as you iterate.
What to contract-test in this stack
1) Supabase Edge Functions as stable integration boundaries
Edge Functions are often the “glue” between your UI and external systems. They should behave like a public API—even if only your own app calls them. Contract tests here focus on:
- HTTP contract: method, headers, status codes, and JSON schema for success and error cases.
- Auth expectations: does the function require a user JWT, a service role key, or a shared secret?
- Business invariants: if a checkout session exists, the function must return the same resource identifiers consistently.
In practice, this means writing tests that execute the Edge Function endpoint and assert on the returned structure and error semantics—not just “it returns 200.” Treat these assertions as a compatibility promise to the React client and to webhook processors downstream.
2) Stripe webhooks as event contracts, not best-effort callbacks
Webhook-based flows fail when you treat events as “fire and forget.” Stripe retries deliveries, events can arrive out of order, and the same event can be sent more than once. Your contract should specify:
- Signature verification behavior: how the webhook handler responds when the signature is missing or invalid.
- Event schema expectations: required fields you rely on (for example, customer, invoice, subscription, metadata keys).
- Idempotency behavior: duplicate events must be safe and produce the same side effects at most once.
The easiest way to make this testable is to formalize a “webhook envelope” in your own code: the validated Stripe event plus a normalized internal shape you store and process. If you want a deeper reliability model for event-driven UI behavior, the same thinking shows up in reliable event-driven frontends with idempotency keys, retries, and dead-letter queues.
3) React frontend state as a contract with the backend
Frontend state is rarely tested as a contract, but it should be—especially when users can refresh, open multiple tabs, or bounce between steps. Your tests should validate:
- State machine transitions: what happens when payment is “pending,” “requires_action,” “paid,” or “failed.”
- Optimistic vs. confirmed state: which UI elements depend on webhook-confirmed truth versus immediate client actions.
- Replay safety: reloading the page must not create another subscription, another invoice, or another database row.
Instead of relying on fragile end-to-end tests alone, build a harness where the React layer consumes mocked-but-contract-accurate responses and events. The point is not to mock everything; it’s to mock only what’s outside the current boundary while still enforcing shape and semantics.
A practical contract-test harness architecture
Define contracts in one place, then enforce them everywhere
A workable approach is to define JSON schemas (or typed interfaces) for three categories:
- Edge Function responses consumed by React.
- Stripe events ingested by your webhook handler.
- Internal domain events you store in Postgres to drive UI and workflows.
Once you have schemas, you can generate test fixtures and validation logic. The harness then becomes a set of repeatable checks that run in CI and locally—ensuring that changes to any one piece don’t silently break another.
Test tiers that map to real failure modes
- Consumer contract tests (frontend-first): React asserts what it needs from Edge Functions. These tests fail if the server response changes shape, status codes change, or fields become nullable unexpectedly.
- Provider contract tests (backend-first): Edge Functions assert they still satisfy the frontend contract and that error modes are stable.
- Event contract tests (webhooks): your webhook handler validates signature handling, schema validation, idempotency persistence, and side effects.
This tiering helps you avoid the common trap: a single “giant E2E test” that’s slow, flaky, and hard to debug. You still keep a small number of E2E tests for the critical path, but contracts catch drift earlier.
Key implementation details that prevent painful regressions
Make idempotency observable in Postgres
Idempotency is not a philosophical principle; it’s a database constraint. For webhook processing, store Stripe event IDs (or a derived idempotency key) with a unique index. Your contract tests should assert:
- Two deliveries of the same event result in one processed record.
- Side effects (like granting entitlements) happen once.
- Repeat delivery returns a safe 2xx response quickly.
Use deterministic “known-good” fixtures for Stripe and Edge Functions
Keep a small library of fixtures representing the cases you actually support: success, known failure, partial data, and unexpected event type. The point is to encode your supported surface area. When you update your product (new plan tiers, new metadata keys, new entitlement logic), you update fixtures and see exactly what breaks.
Validate frontend state transitions with event replay tests
One of the most valuable tests you can add is a replay test: feed the frontend a sequence of internal events (for example: “checkout started” → “payment pending” → “payment succeeded”) and assert that the UI ends up in the right state. Then reorder or duplicate events to prove your UI doesn’t regress under real-world delivery patterns.
This kind of cross-surface verification also connects to broader consistency goals in AI-assisted products; see cross-platform entity consistency testing for AI assistants for adjacent patterns in keeping entities consistent across interfaces.
Where lovable.dev fits in a contract-first workflow
lovable.dev is built around a modern, standard stack that makes this harness approach realistic: React on the frontend, Supabase for auth and Postgres, and a workflow where projects can sync to GitHub from day one. That matters because contract tests are easiest to maintain when the prototype and the production codebase are the same artifact, not two parallel worlds.
In practice, you can iterate quickly in the builder, then lock in integration safety by codifying the contracts your app already assumes. As your Stripe billing, Edge Functions, and UI state grow more complex, the harness becomes the “guardrail” that keeps shipping velocity high without relying on luck.
