PinStripe.Test.Fixtures (PinStripe v0.3.1)
View SourceAutomatically generates and caches Stripe API fixtures for testing.
Fixtures are generated using your Stripe account's API version via the Stripe CLI
and cached in test/fixtures/stripe/. The API version is tracked in .api_version.
⚠️ Important: Side Effects
Fixture generation creates real test data in your Stripe account.
- Resources (customers, products, etc.) are created in test mode
- Objects are marked with "PinStripe Test Fixture" for identification
- Only test mode API keys (starting with
sk_test_) are allowed - Consider committing fixtures to git to avoid regenerating
Requirements
- Stripe CLI installed:
brew install stripe/stripe-cli/stripe - Stripe CLI authenticated:
stripe login - Test mode API key configured (starts with
sk_test_)
Quick Start
Configuration
# config/test.exs
config :pin_stripe,
stripe_api_key: System.get_env("STRIPE_SECRET_KEY")Basic Usage
# In your test
test "creates a customer" do
# Load/generate a customer fixture (use atoms for API resources)
customer = PinStripe.Test.Fixtures.load(:customer)
# Use with Req.Test
Req.Test.stub(PinStripe, fn conn ->
Req.Test.json(conn, customer)
end)
{:ok, response} = Client.create(:customers, %{email: "test@example.com"})
assert response.body["object"] == "customer"
endCustomizing Fixtures
Generate fixtures with specific attributes by passing options:
# Customer with specific email (atoms for API resources)
customer = PinStripe.Test.Fixtures.load(:customer,
email: "alice@example.com",
name: "Alice Smith"
)
# Customer with metadata
customer = PinStripe.Test.Fixtures.load(:customer,
metadata: %{user_id: "123", plan: "premium"}
)
# Webhook event with custom data (strings for webhook events - they have dots)
event = PinStripe.Test.Fixtures.load("customer.created",
data: %{object: %{email: "custom@example.com"}}
)Each unique set of options creates a separate cached fixture file. The filename includes a hash of the options for uniqueness:
test/fixtures/stripe/
customer.json # Base customer
customer-a3f2b9c1.json # Customized customerAPI Version Management
Fixtures match your Stripe account's default API version at the time they're
first generated. The version is stored in test/fixtures/stripe/.api_version.
When you upgrade your Stripe account's API version, run the sync task to clear old fixtures and regenerate them with the new version:
mix pin_stripe.sync_api_versionThis task:
- Detects your account's current API version
- Compares it with the cached version
- Clears all fixtures if the version changed
- Updates the
.api_versionfile
Supported Fixtures
API Resources (Atoms)
:customer- Customer objects:product- Product objects:price- Price objects:subscription- Subscription objects:invoice- Invoice objects:charge- Charge objects (one-time payments):payment_intent- PaymentIntent objects (modern payment flow):refund- Refund objects
Webhook Events (Strings - they have dots)
"customer.created","customer.updated","customer.deleted""customer.subscription.created","customer.subscription.updated","customer.subscription.deleted""invoice.paid","invoice.payment_failed"
Error Responses (Atoms)
:error_404- Not found (resource_missing):error_400- Bad request (invalid_request_error):error_401- Unauthorized (authentication error)error_429- Rate limit exceeded
Testing Best Practices
Commit Fixtures to Git
To avoid generating fixtures in CI or on every developer machine:
# Commit fixtures to version control
git add test/fixtures/stripe
git commit -m "Add Stripe test fixtures"Use Base Fixtures and Modify
Instead of generating many custom fixtures:
# Load base fixture
customer = PinStripe.Test.Fixtures.load(:customer)
# Modify as needed in your test
customer = Map.put(customer, "email", "specific@test.com")Clean Up Test Data
Objects created during fixture generation accumulate in your Stripe test account.
They're marked with pinstripe_fixture: true metadata for easy identification.
To clean up manually:
# List PinStripe test objects
stripe customers list --limit 100 | grep "PinStripe Test Fixture"
# Delete a specific customer
stripe customers delete cus_xxxExamples
Testing API Responses
test "handles customer creation" do
fixture = PinStripe.Test.Fixtures.load(:customer)
Req.Test.stub(PinStripe, fn conn ->
Req.Test.json(conn, fixture)
end)
{:ok, response} = Client.create(:customers, %{email: "test@example.com"})
assert response.body["id"] == fixture["id"]
endTesting Webhook Handlers
test "handles customer.created webhook" do
event = PinStripe.Test.Fixtures.load("customer.created")
conn = build_webhook_conn(event)
conn = MyAppWeb.StripeWebhookController.create(conn, event)
assert conn.status == 200
endTesting Error Handling
test "handles not found error" do
error = PinStripe.Test.Fixtures.load(:error_404)
Req.Test.stub(PinStripe, fn conn ->
conn
|> Plug.Conn.put_status(404)
|> Req.Test.json(error)
end)
assert {:error, %{status: 404}} = Client.read("cus_nonexistent")
endTesting with Multiple Customers
test "handles multiple customers" do
alice = PinStripe.Test.Fixtures.load(:customer, email: "alice@test.com")
bob = PinStripe.Test.Fixtures.load(:customer, email: "bob@test.com")
# Each gets cached separately
assert alice["email"] == "alice@test.com"
assert bob["email"] == "bob@test.com"
end
Summary
Functions
Returns the API version used for cached fixtures.
Detects the current Stripe account's API version.
Lists all available (cached) fixtures.
Loads a fixture by name, generating it if not cached.
Functions
Returns the API version used for cached fixtures.
Returns nil if no .api_version file exists yet.
Examples
iex> # When no .api_version file exists
iex> Application.put_env(:pin_stripe, :fixtures_dir, "test/fixtures/doctest_no_version")
iex> File.rm_rf!("test/fixtures/doctest_no_version")
iex> PinStripe.Test.Fixtures.api_version()
nil
iex> # After creating a version file
iex> Application.put_env(:pin_stripe, :fixtures_dir, "test/fixtures/doctest_version")
iex> File.rm_rf!("test/fixtures/doctest_version")
iex> File.mkdir_p!("test/fixtures/doctest_version")
iex> File.write!("test/fixtures/doctest_version/.api_version", "2024-06-20")
iex> PinStripe.Test.Fixtures.api_version()
"2024-06-20"
iex> File.rm_rf!("test/fixtures/doctest_version")
iex> Application.delete_env(:pin_stripe, :fixtures_dir)
:ok
Detects the current Stripe account's API version.
Makes a quick API call to determine the version. Requires a valid Stripe API key and authenticated Stripe CLI.
Examples
# Requires real Stripe setup
PinStripe.Test.Fixtures.detect_account_api_version()
#=> "2024-06-20"
Lists all available (cached) fixtures.
Returns a list of fixture filenames (without .json extension), sorted alphabetically. Returns an empty list if no fixtures directory exists.
Examples
iex> # When no fixtures exist
iex> Application.put_env(:pin_stripe, :fixtures_dir, "test/fixtures/doctest_empty")
iex> File.rm_rf!("test/fixtures/doctest_empty")
iex> PinStripe.Test.Fixtures.list()
[]
iex> # Lists cached API resource fixtures (not error fixtures which use atoms)
iex> # Error fixtures don't create files, so list() returns only cached API resources
iex> Application.put_env(:pin_stripe, :fixtures_dir, "test/fixtures/stripe")
iex> list = PinStripe.Test.Fixtures.list()
iex> "error_400" in list
true
iex> "error_404" in list
true
iex> Application.delete_env(:pin_stripe, :fixtures_dir)
:ok
Loads a fixture by name, generating it if not cached.
Fixture Types
Error Fixtures (Atoms) - Self-contained, no Stripe CLI required:
- Use atoms:
:error_400,:error_401,:error_402,:error_403,:error_404,:error_409,:error_424,:error_429,:error_500,:error_502,:error_503,:error_504 - Generated instantly (no API calls)
- Match actual Stripe error responses
- Not cached to filesystem
API Resources (Atoms) - Require Stripe CLI and API key:
- Use atoms:
:customer,:product,:price,:subscription,:invoice,:charge,:payment_intent,:refund - Created via Stripe API
- Cached to filesystem
- Options passed to Stripe CLI during creation
Webhook Events (Strings) - Require Stripe CLI and API key:
- Use strings with dots:
"customer.created","invoice.paid", etc. - Generated via Stripe CLI trigger
- Cached to filesystem
- Options used to modify event data after generation
Options
Options are used to customize fixtures. Each unique combination of options creates a separate cached fixture (for API resources/webhooks only).
Examples
Error fixtures use atoms and are self-contained:
iex> error = PinStripe.Test.Fixtures.load(:error_404)
iex> error["error"]["type"]
"invalid_request_error"
iex> error["error"]["code"]
"resource_missing"
iex> error = PinStripe.Test.Fixtures.load(:error_400)
iex> error["error"]["type"]
"invalid_request_error"
iex> error["error"]["code"]
"parameter_invalid_empty"
iex> error = PinStripe.Test.Fixtures.load(:error_401)
iex> error["error"]["type"]
"invalid_request_error"
iex> error = PinStripe.Test.Fixtures.load(:error_429)
iex> error["error"]["type"]
"rate_limit_error"API resources use atoms and webhook events use strings (both require Stripe CLI):
# API resources use atoms - require real Stripe setup
PinStripe.Test.Fixtures.load(:customer)
#=> %{"id" => "cus_...", "object" => "customer", ...}
PinStripe.Test.Fixtures.load(:customer, email: "test@example.com")
#=> %{"id" => "cus_...", "email" => "test@example.com", ...}
# Webhook events use strings (they have dots)
PinStripe.Test.Fixtures.load("customer.created")
#=> %{"id" => "evt_...", "type" => "customer.created", ...}