neilberkman
PaperTiger - a stateful mock Stripe server for Elixir testing
We just published PaperTiger, a mock Stripe server we built to solve real pain points in our test suite.
The Problem
Testing Stripe integrations in Elixir (and in any language, really!) has some rough edges:
-
Stripe’s official stripe-mock is stateless - you can’t create a customer and then fetch it, which makes testing realistic flows impossible
-
Stripe’s webhook sandbox is limited - 5 endpoints max, dashboard-only management, no programmatic control
-
Shared test mode causes collisions - CI runs interfere with manual testing, leading to flaky tests
-
No time control - testing subscription renewals means waiting or hacking around Stripe’s billing cycle
We needed something that actually behaves like Stripe - stateful resources, proper webhook chains, and time control for billing cycles.
What PaperTiger Does
-
Stateful API - Create a customer, attach a payment method, subscribe them, and it all persists (in ETS)
-
Automatic webhook delivery - Resource changes emit properly-signed webhooks with retry logic
-
Time control - Accelerate time for subscription testing, or use manual mode for precise control
-
BillingEngine - Simulates subscription lifecycle: period rollovers, invoice creation, payment processing
-
Chaos mode - Inject payment failures with configurable decline codes for testing error paths
-
Contract testing - Run the same tests against PaperTiger and real Stripe to verify behavior matches
Quick Example
setup do
{:ok, config: PaperTiger.stripity_stripe_config()}
end
test "subscription billing cycle", %{config: config} do
{:ok, customer} = Stripe.Customer.create(%{email: "test@example.com"}, config)
{:ok, sub} = Stripe.Subscription.create(%{customer: customer.id, ...}, config)
assert sub.status == "active"
# Advance time past the billing period
PaperTiger.Clock.advance_days(32)
# Subscription renewed, invoice created and paid
{:ok, invoices} = Stripe.Invoice.list(%{customer: customer.id}, config)
assert length(invoices.data) == 2
end
Links
https://github.com/EnaiaInc/paper_tiger
We’d welcome feedback, issues, or contributions. If you’ve dealt with similar Stripe testing headaches, hopefully this helps.
Most Liked
neilberkman
PaperTiger is now v1.0.0 and is much more usable than the early versions. For us internally, this has been a major win: we replaced a large amount of custom Stripe mocking code with PaperTiger-based tests, and it’s also been very useful in development mode. If you used it early on and had any difficulties you may want to try again as it should be significantly improved. Details in the changelog.
neilberkman
PaperTiger is now v1.2.1, and it has gotten a lot more useful for real billing-test suites since the 1.0.0 announcement.
PaperTiger is a stateful local Stripe mock for Elixir apps. It now covers enough of Stripe’s billing surface that you can test much more of your app without hitting Stripe test mode.
More real billing flows work locally
- PaymentIntent confirm/cancel/capture, including Charges and BalanceTransactions.
- SetupIntent confirm/cancel flows, Checkout Session updates/line items, and invoice send/mark-uncollectible/attach-payment flows.
- Payment Links, Billing Portal sessions/configurations, Promotion Codes, Credit Notes, Customer Balance, and basic Connect platform flows.
The mock is stricter, and models Stripe more faithfully
- Stripe-style search endpoints and much better list filtering.
- More realistic SubscriptionSchedule behavior.
- Idempotency keys now reject same-key/different-payload requests like Stripe does.
Testing hooks are nicer now
- Pluggable webhook delivery, for apps that want to own durable webhook delivery/retry.
- Signed webhook request helpers, so you can test the same verification code you use in production.
- Request spy helpers, so tests can assert exactly what your app sent to PaperTiger.
Full changelog: Changelog — PaperTiger v1.2.1
Hex package: paper_tiger | Hex
Popular in Announcing
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #advent-of-code
- #elixirconf-us
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #performance








