All posts
Aug 14, 2026·7 min

Differential testing a Shopify discount rule

ShopifyTesting

Mixly is a Shopify app that lets merchants build BOGO and bundle discounts, and it has to answer the same eligibility question twice, in two different places. The storefront decides whether to advertise an offer, rendered in Liquid inside the merchant’s theme. Checkout decides whether to actually grant it, inside a Shopify Function compiled to WebAssembly. Same rule, two implementations, two languages, and nothing forcing them to agree.

Liquid can’t import TypeScript, so the storefront’s gate logic is a hand-transcription of the Function’s rule, not a shared library. When the two drift, the failure is quiet and expensive: the storefront keeps promising a discount the Function will decline to grant, and the first person to notice is a customer at checkout, not us.

The fix wasn’t more hand-written test cases — it was a differential fuzz test. `liquid-mirror-sweep.test.ts` reimplements the Liquid gate logic in TypeScript and diffs its verdict against the real allocator across every combination of 400 generated offers and 729 synthetic carts: five BUY items × five GET items × eight mode/value pairs × two item orderings, against every quantity-0/1/2 state of six variants. 291,600 rule × cart pairs, each independently recomputed and compared rather than trusted to agree by construction.

It caught a bug that four earlier rounds of hand-tracing had missed: 4,569 cases where the storefront kept a BOGO offer advertised as live after the Function would have paid it zero. The honest caveat is that the sweep only proves the TypeScript transcription agrees with the allocator — it never parses the actual `.liquid` files, because it can’t. Three Liquid files and one test still have to be kept in sync by hand, and a drift between them wouldn’t fail this test either. The number of pairs wasn’t the point; the point is that when two implementations have to agree, you test them against each other, not each one alone.