How to Write Test Cases for Subscription Purchase (With Examples)
How to Write Test Cases for Subscription Purchase (With Examples) starts with understanding the subscription flow as a sequence of state changes triggered by user actions and system responses. This gu
How to Write Test Cases for Subscription Purchase (With Examples) starts with understanding the subscription flow as a sequence of state changes triggered by user actions and system responses. This guide walks you through the anatomy of a high‑signal test case, shows how to populate a concrete test matrix, explains data‑setup techniques, and connects manual design with autonomous exploration so you achieve real coverage of the subscription purchase path.
How to Write Test Cases for Subscription Purchase (With Examples): Overview
Why subscription purchase testing matters
Subscription purchase is a revenue‑critical path. A single failure—whether a declined card, a mis‑calculated prorated amount, or an inaccessible checkout button—can cause immediate churn, refunds, or compliance penalties. Because the flow touches UI, business logic, payment gateway integrations, tax calculation, and entitlement services, defects often hide in interactions between layers rather than in isolated unit tests. A well‑crafted test case suite therefore validates end‑to‑end correctness, surfaces regression risks early, and provides traceable evidence for auditors and product owners.
Core concepts: test case anatomy
A test case is an actionable specification that answers three questions:
- Precondition – what must be true before the test begins (account state, entitlements, device locale, etc.).
- Steps – the ordered interactions a user or automation script performs.
- Expected Result – the observable outcome that confirms the system behaved correctly (UI message, database row, API response, entitlement grant).
Additional fields that add value for traceability and prioritization:
- Test ID – unique identifier, often prefixed with the feature area (e.g.,
SUB‑001). - Linked Requirement – reference to the user story, epic, or regulation (GDPR, PCI‑DSS) the test validates.
- Priority / Severity – helps triage when time is limited.
- Automation Flag – indicates whether the case is manual‑only, automatable, or already automated.
When you write a test case, start with the precondition because it determines the data you need to seed. Then enumerate each UI interaction or API call as a discrete step, using imperative language (“Tap the Subscribe button”, “Send a POST to /v1/subscriptions with body …”). Finally, phrase the expected result as a verifiable assertion (“The screen shows ‘Subscription active until 2025‑12‑31’”, “The entitlement service returns status: ACTIVE”, “A webhook subscription.created is delivered to the configured endpoint”).
How to Write Test Cases for Subscription Purchase (With Examples): Building the Test Matrix
Defining preconditions
Preconditions for subscription purchase typically involve:
- User account – exists, email verified, no active subscription unless testing renewal or upgrade.
- Payment method – either a valid test card token, a deliberately invalid token, or no payment method attached.
- Device/context – locale, currency, timezone, and any feature flags that affect pricing or trial availability.
- Entitlement state – ensures the user is not already entitled to the product being purchased (unless testing duplicate purchase handling).
- Backend state – mock or stub services configured to return specific HTTP status codes (e.g., 200 for success, 402 for payment required, 500 for gateway timeout).
Document each precondition as a bullet list attached to the test case. When you automate, translate these bullets into fixture setup code (e.g., a beforeEach that creates a user via API, attaches a test card, and clears any existing subscription).
Structuring steps and expected results
Steps should be atomic and observable. Avoid bundling multiple UI actions into a single step unless they are inseparable (e.g., “Fill the credit‑card form and tap Pay” can be split if you need to validate intermediate validation messages). Use the same terminology as the product specification (“Select Monthly plan”, “Enter Promo code SAVE10”). Expected results must be testable by an observer or an automated check: UI text, HTTP status code, database entry, webhook payload, or error toast.
Example test case format
Below is a minimal markdown template you can copy into your test‑management tool:
**Test ID:** SUB-001
**Title:** Successful monthly subscription with valid card
**Precondition:**
- User `test_user_01` exists, email verified, no active subscription.
- Test card `4242424242424242` (Stripe test token) attached to user.
- Locale set to `en-US`, currency USD.
**Steps:**
1. Navigate to the subscription screen.
2. Select the **Monthly** plan ($9.99/month).
3. Tap **Subscribe**.
4. Confirm the payment sheet shows the test card.
5. Tap **Pay**.
**Expected Result:**
- Screen displays **“Subscription active until <next billing date>”**.
- Entitlement API returns `status: ACTIVE` for the product.
- Webhook `subscription.created` received with `amount: 999` (cents).
- No error toast appears.
**Linked Requirement:** REQ‑SUB‑01 (User can purchase a monthly subscription).
**Priority:** P1 (Critical).
**Severity:** S1 (Blocker if fails).
**Automation:** Yes (Playwright + API fixtures).
Repeat this structure for each scenario you need to cover. The next section provides a populated matrix of 20+ cases.
How to Write Test Cases for Subscription Purchase (With Examples): Positive, Negative, Edge Cases
Positive flow test cases
Positive cases verify that the happy path works under normal conditions. They also serve as baselines for negative and edge testing.
| ID | Title | Precondition (summary) | Steps (summary) | Expected Result |
|---|---|---|---|---|
| SUB-001 | Successful monthly subscription with valid card | Verified user, no active sub, Stripe test card attached | Choose Monthly → Subscribe → Confirm card → Pay | Subscription active, entitlement ACTIVE, webhook received |
| SUB-002 | Successful annual subscription with promo code | Verified user, no active sub, promo YEAR20 gives 20 % off, card attached | Choose Annual → Enter promo → Subscribe → Pay | Price shows 20 % discount, entitlement ACTIVE for 1 year |
| SUB-003 | Subscription upgrade from monthly to annual | User has active monthly sub, card attached | Navigate to Manage → Change plan → Select Annual → Confirm prorated charge → Pay | New annual entitlement, prorated credit applied, next billing date adjusted |
| SUB-004 | Subscription downgrade from annual to monthly | User has active annual sub, card attached | Manage → Change plan → Select Monthly → Confirm refund of unused period → Pay | Monthly entitlement active, refund issued to original payment method |
| SUB-005 | Renewal after trial expiration (no card on file) | User completed 7‑day free trial, no payment method, trial ended | App detects expired trial → Prompt to add card → User adds valid card → Confirm subscription | Subscription becomes active, entitlement granted, first billing occurs immediately |
| SUB-006 | Renewal with automatic card‑on‑file (token) | User has active trial, card token stored from sign‑up | Trial ends → System automatically charges stored token | Subscription renews, entitlement continues, webhook subscription.updated |
| SUB-007 | Successful purchase with Apple In‑App Purchase | User on iOS, sandbox Apple ID configured, no active sub | Tap Subscribe → Apple ID sheet → Authenticate → Confirm purchase | Receipt validated, entitlement ACTIVE, server records purchase |
| SUB-008 | Successful purchase with Google Play Billing | User on Android, test account, no active sub | In‑app billing flow → Select plan → Confirm purchase | Purchase token verified, entitlement ACTIVE |
Negative validation test cases
Negative cases check that the system correctly rejects invalid input, prevents fraudulent attempts, and shows helpful messages.
| ID | Title | Precondition (summary) | Steps (summary) | Expected Result |
|---|---|---|---|---|
| SUB-009 | Attempt purchase with expired card | User has test card 4000000000000069 (expired) attached | Choose plan → Subscribe → Pay | Payment gateway returns expired_card, UI shows “Card expired” toast, no entitlement created |
| SUB-010 | Attempt purchase with insufficient funds | Card 4000000000009995 (insufficient funds) attached | Same as SUB-009 | Gateway returns insufficient_funds, error toast, no entitlement |
| SUB-011 | Attempt purchase with disabled 3DS required | Card that triggers 3DS authentication failure | Initiate payment → 3DS challenge fails | Payment declined, UI shows “Authentication failed”, no entitlement |
| SUB-012 | Attempt purchase without accepting terms | User has not checked the Terms of Service checkbox | Fill payment details → Tap Pay (checkbox unchecked) | Inline validation error: “You must accept the terms”, Pay button remains disabled |
| SUB-013 | Attempt purchase with malformed promo code | Promo code !!INVALID!! | Enter promo → Apply | Inline error: “Invalid promo code”, price unchanged |
| SUB-014 | Attempt purchase when subscription already active | User already has active monthly sub | Navigate to Subscribe screen → Try to purchase same plan | Info toast: “You already have an active subscription”, purchase blocked |
| SUB-015 | Attempt purchase with revoked entitlement due to fraud | User’s account flagged for fraud, entitlement service returns status: SUSPENDED | Attempt any purchase | API returns 403 Forbidden, UI shows “Account restricted” |
| SUB-016 | Attempt purchase with network timeout | Mock payment gateway configured to delay response >30 s | Tap Pay | Loading spinner shown, after timeout error toast: “Connection timed out. Please try again.” |
| SUB-017 | Attempt purchase with server returns HTTP 500 from entitlement service | Entitlement service stub returns 500 | Complete payment successfully | Despite successful charge, UI shows “Something went wrong. Please contact support.”, no entitlement |
Edge and boundary condition test cases
Edge cases probe limits, unusual data, and timing conditions that rarely appear in unit tests but surface in production.
| ID | Title | Precondition (summary) | Steps (summary) | Expected Result |
|---|---|---|---|---|
| SUB-018 | Purchase with maximum allowed promo‑code length | Promo field accepts up to 32 chars; use 32‑char alphanumeric string | Enter 32‑char promo → Apply | Promo accepted, discount applied correctly |
| SUB-019 | Purchase with zero‑price trial that converts to paid | Trial plan price $0, after 3 days converts to $4.99/month | Start trial → Wait 3 days (simulate via clock mock) → System attempts charge | First paid charge occurs, entitlement remains ACTIVE |
| SUB-020 | Purchase with currency switching mid‑flow | User locale set to EUR, but user manually selects USD price toggle before paying | Switch currency to USD → Select plan → Pay | Price shown in USD, charge processed in USD, entitlement granted |
| SUB-021 | Purchase with leap‑year‑29‑Feb billing date | Annual plan set to bill on 2024‑02‑29 (leap year) | Start annual subscription on 2023‑03‑01 | Next billing date correctly set to 2025‑02‑28 (non‑leap year) or 2025‑02‑29 depending on business rule |
| SUB-022 | Purchase with timezone‑dependent trial end | Trial ends at 00:00 UTC; user in UTC‑12 (latest timezone) | Start trial at 23:45 local time → Wait 15 min | Trial ends correctly for user’s local time, entitlement transitions at proper moment |
| SUB-023 | Purchase with simultaneous device sessions | Same user logged in on two devices | Device A initiates purchase → Device B attempts to purchase same plan concurrently | Only one transaction succeeds; second device shows “Purchase in progress” or “Already subscribed” |
| SUB-024 | Purchase with corrupted receipt (iOS) | Manually tamper with Apple receipt signature | Submit receipt to validation endpoint | Validation fails, UI shows “Unable to verify purchase”, no entitlement |
| SUB-025 | Purchase with GDPR consent withdrawn mid‑flow | User has given consent, then withdraws before confirming payment | Withdraw consent via privacy settings → Attempt purchase | Purchase blocked, message: “Consent required to process payment” |
| SUB-026 | Purchase with accessibility‑mode navigation | User employs TalkBack (Android) or VoiceOver (iOS) | Navigate using screen‑reader gestures to select plan and confirm payment | All controls are announced, purchase completes successfully |
| SUB-027 | Purchase with low‑memory device condition | Emulate low RAM (< 512 MB) | Run purchase flow | UI remains responsive, no crash, payment completes or shows appropriate error |
| SUB-028 | Purchase with interrupted network (airplane mode) | Enable airplane mode after tapping Pay | Tap Pay → Immediately enable airplane mode | Payment fails gracefully, retry option presented, no partial entitlement grant |
> Note: The table above contains 28 test cases. Feel free to trim or expand based on your product’s specific rules (e.g., number of promo‑code types, supported payment methods, regional tax variations).
Test Data Management for Subscription Scenarios
Static vs dynamic data
Static data (e.g., a list of known good/test cards) is useful for reproducibility. Store these in a version‑controlled JSON or YAML file that your test fixtures load. Dynamic data—such as timestamps for trial expiration or unique promo‑code generation—should be created at runtime to avoid collisions when tests run in parallel.
# conftest.py – pytest fixture for a fresh user with a random email
import uuid, random
import requests
API_BASE = "https://api.example.com"
@pytest.fixture
def fresh_user():
email = f"test_{uuid.uuid4()}@example.com"
pw = "TempPass123!"
resp = requests.post(f"{API_BASE}/users",
json={"email": email, "password": pw})
assert resp.status_code == 201
user_id = resp.json()["id"]
# attach a valid Stripe test token
card_tok = "tok_visa" # predefined Stripe test token
requests.post(f"{API_BASE}/users/{user_id}/payment",
json={"token": card_tok})
return {"id": user_id, "email": email, "token": card_tok}
Using test data generators
Libraries such as Faker or bogus can produce realistic names, addresses, and phone numbers for fields that affect tax calculation or fraud checks.
from faker import Faker
fake = Faker()
def random_address():
return {
"line1": fake.street_address(),
"city": fake.city(),
"state": fake.state_abbr(),
"postal_code": fake.postcode(),
"country": fake.country_code()
}
Mocking payment gateways
Instead of hitting real Stripe or Braintree endpoints in CI, run a lightweight mock server (e.g., stripe-mock or wiremock) that returns programmable responses. This lets you simulate declines, 3DS challenges, and webhook delays without incurring costs.
# start stripe-mock in Docker
docker run -p 12111:12111 stripe/stripe-mock:latest
# then configure your test client to point to localhost:12111
export STRIPE_API_BASE=http://localhost:12111
When you need to test webhook handling, configure the mock to POST to a local endpoint you control (e.g., an ngrok tunnel or a test‑server that records payloads). Assert that the received webhook matches the expected event type and payload schema.
Prioritization and Risk-Based Testing
Impact vs likelihood matrix
Assign each test case a risk score = Impact (1‑5) × Likelihood (1‑5). Impact reflects business damage (revenue loss, compliance violation). Likelihood reflects how often the condition occurs in production or how易错 the code is.
| Impact \ Likelihood | 1 Rare | 2 Unlikely | 3 Possible | 4 Likely | 5 Frequent |
|---|---|---|---|---|---|
| 5 Catastrophic | 5 | 10 | 15 | 20 | 25 |
| 4 Major | 4 | 8 | 12 | 16 | 20 |
| 3 Moderate | 3 | 6 | 9 | 12 | 15 |
| 2 Minor | 2 | 4 | 6 | 8 | 10 |
| 1 Insignificant | 1 | 2 | 3 | 4 | 5 |
Prioritize tests with scores ≥ 15 for early execution in each sprint. For example, SUB-001 (happy path) scores 25, while SUB-018 (max‑length promo) might score 6 (low impact, low likelihood) and can be deferred to a regression sprint.
Assigning severity and priority
- Severity (technical): S1 = crash or data corruption, S2 = functional block, S3 = UI glitch, S4 = cosmetic.
- Priority (business): P1 = must‑fix before release, P2 = high‑value, P3 = medium, P4 = low.
Map risk scores to these labels using a simple rule‑set in your test‑management tool (e.g., score ≥ 20 → S1/P1, 15‑19 → S2/P2, etc.). This automation reduces manual triage overhead.
Traceability to requirements
Every test case should reference at least one requirement ID. Use a bidirectional traceability matrix:
| Requirement ID | Description | Covered By Test IDs |
|---|---|---|
| REQ‑SUB‑01 | User can purchase a monthly subscription | SUB-001, SUB-009, SUB-010, SUB-012 |
| REQ‑SUB‑02 | User can apply a promotional discount | SUB-002, SUB-013 |
| REQ‑SUB‑03 | Subscription can be upgraded/downgraded | SUB-003, SUB-004 |
| REQ‑SUB‑04 | Trial converts to paid subscription | SUB-005, SUB-019 |
| REQ‑SUB‑05 | System handles payment gateway errors | SUB-009, SUB-010, SUB-015, SUB-016 |
| REQ‑SUB‑06 | Accessibility compliance (WCAG 2.1 AA) | SUB-026 |
| REQ‑SUB‑07 | Data‑privacy consent enforcement | SUB-025 |
When a requirement changes, you can instantly see which tests need review or addition.
Manual vs Automated Execution Strategies
When to keep tests manual
Certain scenarios are costly to automate reliably, such as:
- Visual regression of custom-designed promotional banners.
- Manual exploratory flows that rely on human judgment (e.g., assessing frustration signals).
- One‑off regulatory checks that require a human auditor’s sign‑off.
Keep these as exploratory test sessions with a short charter (e.g., “Verify that the promotional banner does not obscure the Pay button on any screen size”). Document observations in a session‑based test management tool and promote any reproducible defect to an automated regression test if the root cause is a deterministic bug.
Choosing automation frameworks
For subscription purchase, you typically need:
- UI automation for flow validation (button taps, form fills).
- API automation for direct entitlement and webhook verification.
- Performance/load checks for concurrent purchase attempts.
A common stack:
- Playwright (or Cypress) for cross‑browser UI tests on web.
- Appium (or Espresso/XCUITest) for native mobile.
- REST‑Assured or requests (Python) for API calls.
- Karate or Postman/Newman for BDD‑style API scenarios.
- k6 or Locust for load‑testing the purchase endpoint.
Below is a concise Playwright (TypeScript) example that covers SUB-001 and asserts the entitlement via an API call after the UI flow.
// tests/subscription-purchase.spec.ts
import { test, expect } from '@playwright/test';
import axios from 'axios';
test.describe('Subscription purchase flow', () => {
test('happy path monthly subscription', async ({ page }) => {
// 1. Login via API to get auth cookie (skip UI login for speed)
await page.context().addCookies([
{ name: 'session', value: await getAuthCookie(), domain: '.example.com', path: '/' }
]);
// 2. Navigate to subscription page
await page.goto('/subscribe');
// 3. Select Monthly plan
await page.selectOption('select#plan', 'monthly');
// 4. Assert price displayed
await expect(page.locator('#price')).toHaveText('$9.99');
// 5. Click Subscribe
await page.click('button#subscribe');
// 6. Fill Stripe test card (using Stripe's test element selectors)
await page.fill('input[name="cardnumber"]', '4242424242424242');
await page.fill('input[name="exp-date"]', '12/34');
await page.fill('input[name="cvc"]', '123');
await page.fill('input[name="postal"]', '12345');
// 7. Submit payment
await page.click('button#pay');
// 8. Wait for success message
await expect(page.locator('text=Subscription active')).toBeVisible();
// 9. Verify entitlement via backend API
const entitlementResp = await axios.get(
`https://api.example.com/users/me/entitlements`,
{ headers: { Authorization: `Bearer ${await getAuthToken()}` } }
);
expect(entitlementResp.data).toEqual(
expect.arrayContaining([
expect.objectContaining({ productId: 'monthly_plan', status: 'ACTIVE' })
])
);
});
});
async function getAuthCookie(): Promise<string> {
// implement login via API and return session cookie
return 'dummy-cookie';
}
async function getAuthToken(): Promise<string> {
// retrieve JWT or OAuth token
return 'dummy-token';
}
Example automated test with Appium (Android)
If you need to validate native Android flows, the following snippet shows a test for SUB-005 (trial expiration → card entry).
// SubscriptionPurchaseTest.java
import io.appium.java_client.AppiumDriver;
import io.appium.java_client.MobileBy;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.remote.DesiredCapabilities;
import java.net.URL;
import java.util.concurrent.TimeUnit;
public class SubscriptionPurchaseTest {
private AppiumDriver driver;
@BeforeEach
public void setUp() throws Exception {
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability("platformName", "Android");
caps.setCapability("deviceName", "Pixel_4_API_33");
caps.setCapability("appPackage", "com.example.app");
caps.setCapability("appActivity", ".MainActivity");
caps.setCapability("automationName", "UiAutomator2");
driver = new AppiumDriver<>(new URL("http://localhost:4723/wd/hub"), caps);
driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
// pre‑condition: create a trial user via API and log in
TrialUserHelper.createTrialUser(driver);
}
@AfterEach
public void tearDown() {
if (driver != null) driver.quit();
}
@Test
public void trialExpiresThenUserAddsCardAndSubscribes() {
// 1. Navigate to subscription screen
driver.findElement(By.id("nav_subscribe")).click();
// 2. Verify trial banner is shown
org.junit.jupiter.api.Assertions.assertTrue(
driver.findElement(By.id("trial_banner")).isDisplayed(),
"Trial banner should be visible"
);
// 3. Wait for trial to expire (mocked via backend; we fast‑forward clock)
TrialUserHelper.fastForwardTrial(driver, 8); // days
// 4. App should now show “Add payment method” screen
org.junit.jupiter.api.Assertions.assertTrue(
driver.findElement(By.id("add_payment_screen")).isDisplayed(),
"Add payment screen should appear after trial end"
);
// 5. Enter valid test card
driver.findElement(By.id("card_number")).sendKeys("4242424242424242");
driver.findElement(By.id("exp_date")).sendKeys("12/34");
driver.findElement(By.id("cvc")).sendKeys("123");
driver.findElement(By.id("postal_code")).sendKeys("10001");
// 6. Press Subscribe
driver.findElement(By.id("btn_subscribe")).click();
// 7. Verify success toast and entitlement via API
org.junit.jupiter.api.Assertions.assertTrue(
driver.findElement(By.id("toast_success")).getText()
.contains("Subscription active")
);
String entitlement = ApiHelper.getEntitlement(driver);
org.junit.jupiter.api.Assertions.assertTrue(
entitlement.contains("\"status\":\"ACTIVE\"")
);
}
}
These snippets illustrate how you can couple UI interactions with backend assertions, ensuring that a successful purchase not only shows the right screen but also updates the entitlement service and fires the expected webhook.
Leveraging Autonomous Exploration with SUSA
How SUSA complements manual test cases
SUSA (the autonomous QA platform) explores an app without pre‑written scripts, exercising a variety of user personas. When you point SUSA at a build that includes the subscription purchase flow, it will:
- Generate random taps, scrolls, and text inputs that mimic curious, impatient, novice, power‑user, and accessibility‑focused personas.
- Detect crashes, ANRs, dead buttons, WCAG violations, and security issues as it traverses the UI.
- Automatically trace high‑level flows (login → subscribe → payment confirmation) and assign PASS/FAIL based on observable success criteria (e.g., presence of a “Subscription active” toast, HTTP 200 from entitlement endpoint).
Because SUSA learns from each run, repeated executions reduce redundant exploration and focus on previously unseen states (e.g., edge‑case screens that appear only after a specific sequence of promo‑code entries or after a network interruption).
Configuring personas for subscription flow
In the SUSA dashboard, create a Subscription Purchase test suite and enable the following personas:
- Curious – tries every promotional banner, taps on help icons, and attempts to apply multiple promo codes sequentially.
- Impatient – rapidly taps the Subscribe button before forms are fully loaded, testing for race conditions.
- Novice – follows the default UI flow without shortcuts, often missing optional fields (e.g., leaving the promo‑code blank).
- Power user – uses keyboard shortcuts (if web), copy‑pastes card numbers from clipboard, and attempts to reuse a previously used promo‑code.
- Accessibility – relies on screen‑reader navigation, ensuring all controls are labeled and operable via TalkBack/VoiceOver.
- Adversarial – submits malformed data (SQL injection attempts in name fields, extremely long strings, special characters) to surface validation or security flaws.
Assign each persona a weight that reflects your user‑base distribution (e.g., 40 % curious, 30 % impatient, 20 % novice, 5 % power user, 5 % accessibility). SUSA will then allocate exploration steps accordingly.
Interpreting SUSA findings
After a run, SUSA produces a findings report grouped by severity. For subscription purchase you’ll typically see:
- Crash logs with stack traces pointing to null‑pointer exceptions when a promo‑code field is left empty and the backend expects a string.
- ANR traces showing the UI thread blocked while loop stuck waiting for a synchronous payment gateway call.
- Dead button reports where the “Apply promo” button becomes disabled after a network error and never re‑enables.
- WCAG violations such as missing aria‑labels on the card‑number input or insufficient contrast on error toasts.
- Security findings like clear‑text logging of the full card number in console output (detected via string‑scan on logs).
Each finding includes a reproduction steps section that SUSA derived from its exploration trace. You can copy those steps directly into a new manual test case or an automated script. Over time, you’ll notice that SUSA’s discovered paths start to overlap with your manually written cases, confirming coverage, while its unique paths highlight gaps you hadn’t considered
Test Your App Autonomously
Upload your APK or URL. SUSA explores like 10 real users — finds bugs, accessibility violations, and security issues. No scripts.
Try SUSA Free