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

January 11, 2026 · 16 min read · How-To Guides

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:

  1. Precondition – what must be true before the test begins (account state, entitlements, device locale, etc.).
  2. Steps – the ordered interactions a user or automation script performs.
  3. 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:

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:

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.

IDTitlePrecondition (summary)Steps (summary)Expected Result
SUB-001Successful monthly subscription with valid cardVerified user, no active sub, Stripe test card attachedChoose Monthly → Subscribe → Confirm card → PaySubscription active, entitlement ACTIVE, webhook received
SUB-002Successful annual subscription with promo codeVerified user, no active sub, promo YEAR20 gives 20 % off, card attachedChoose Annual → Enter promo → Subscribe → PayPrice shows 20 % discount, entitlement ACTIVE for 1 year
SUB-003Subscription upgrade from monthly to annualUser has active monthly sub, card attachedNavigate to Manage → Change plan → Select Annual → Confirm prorated charge → PayNew annual entitlement, prorated credit applied, next billing date adjusted
SUB-004Subscription downgrade from annual to monthlyUser has active annual sub, card attachedManage → Change plan → Select Monthly → Confirm refund of unused period → PayMonthly entitlement active, refund issued to original payment method
SUB-005Renewal after trial expiration (no card on file)User completed 7‑day free trial, no payment method, trial endedApp detects expired trial → Prompt to add card → User adds valid card → Confirm subscriptionSubscription becomes active, entitlement granted, first billing occurs immediately
SUB-006Renewal with automatic card‑on‑file (token)User has active trial, card token stored from sign‑upTrial ends → System automatically charges stored tokenSubscription renews, entitlement continues, webhook subscription.updated
SUB-007Successful purchase with Apple In‑App PurchaseUser on iOS, sandbox Apple ID configured, no active subTap Subscribe → Apple ID sheet → Authenticate → Confirm purchaseReceipt validated, entitlement ACTIVE, server records purchase
SUB-008Successful purchase with Google Play BillingUser on Android, test account, no active subIn‑app billing flow → Select plan → Confirm purchasePurchase 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.

IDTitlePrecondition (summary)Steps (summary)Expected Result
SUB-009Attempt purchase with expired cardUser has test card 4000000000000069 (expired) attachedChoose plan → Subscribe → PayPayment gateway returns expired_card, UI shows “Card expired” toast, no entitlement created
SUB-010Attempt purchase with insufficient fundsCard 4000000000009995 (insufficient funds) attachedSame as SUB-009Gateway returns insufficient_funds, error toast, no entitlement
SUB-011Attempt purchase with disabled 3DS requiredCard that triggers 3DS authentication failureInitiate payment → 3DS challenge failsPayment declined, UI shows “Authentication failed”, no entitlement
SUB-012Attempt purchase without accepting termsUser has not checked the Terms of Service checkboxFill payment details → Tap Pay (checkbox unchecked)Inline validation error: “You must accept the terms”, Pay button remains disabled
SUB-013Attempt purchase with malformed promo codePromo code !!INVALID!!Enter promo → ApplyInline error: “Invalid promo code”, price unchanged
SUB-014Attempt purchase when subscription already activeUser already has active monthly subNavigate to Subscribe screen → Try to purchase same planInfo toast: “You already have an active subscription”, purchase blocked
SUB-015Attempt purchase with revoked entitlement due to fraudUser’s account flagged for fraud, entitlement service returns status: SUSPENDEDAttempt any purchaseAPI returns 403 Forbidden, UI shows “Account restricted”
SUB-016Attempt purchase with network timeoutMock payment gateway configured to delay response >30 sTap PayLoading spinner shown, after timeout error toast: “Connection timed out. Please try again.”
SUB-017Attempt purchase with server returns HTTP 500 from entitlement serviceEntitlement service stub returns 500Complete payment successfullyDespite 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.

IDTitlePrecondition (summary)Steps (summary)Expected Result
SUB-018Purchase with maximum allowed promo‑code lengthPromo field accepts up to 32 chars; use 32‑char alphanumeric stringEnter 32‑char promo → ApplyPromo accepted, discount applied correctly
SUB-019Purchase with zero‑price trial that converts to paidTrial plan price $0, after 3 days converts to $4.99/monthStart trial → Wait 3 days (simulate via clock mock) → System attempts chargeFirst paid charge occurs, entitlement remains ACTIVE
SUB-020Purchase with currency switching mid‑flowUser locale set to EUR, but user manually selects USD price toggle before payingSwitch currency to USD → Select plan → PayPrice shown in USD, charge processed in USD, entitlement granted
SUB-021Purchase with leap‑year‑29‑Feb billing dateAnnual plan set to bill on 2024‑02‑29 (leap year)Start annual subscription on 2023‑03‑01Next billing date correctly set to 2025‑02‑28 (non‑leap year) or 2025‑02‑29 depending on business rule
SUB-022Purchase with timezone‑dependent trial endTrial ends at 00:00 UTC; user in UTC‑12 (latest timezone)Start trial at 23:45 local time → Wait 15 minTrial ends correctly for user’s local time, entitlement transitions at proper moment
SUB-023Purchase with simultaneous device sessionsSame user logged in on two devicesDevice A initiates purchase → Device B attempts to purchase same plan concurrentlyOnly one transaction succeeds; second device shows “Purchase in progress” or “Already subscribed”
SUB-024Purchase with corrupted receipt (iOS)Manually tamper with Apple receipt signatureSubmit receipt to validation endpointValidation fails, UI shows “Unable to verify purchase”, no entitlement
SUB-025Purchase with GDPR consent withdrawn mid‑flowUser has given consent, then withdraws before confirming paymentWithdraw consent via privacy settings → Attempt purchasePurchase blocked, message: “Consent required to process payment”
SUB-026Purchase with accessibility‑mode navigationUser employs TalkBack (Android) or VoiceOver (iOS)Navigate using screen‑reader gestures to select plan and confirm paymentAll controls are announced, purchase completes successfully
SUB-027Purchase with low‑memory device conditionEmulate low RAM (< 512 MB)Run purchase flowUI remains responsive, no crash, payment completes or shows appropriate error
SUB-028Purchase with interrupted network (airplane mode)Enable airplane mode after tapping PayTap Pay → Immediately enable airplane modePayment 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 \ Likelihood1 Rare2 Unlikely3 Possible4 Likely5 Frequent
5 Catastrophic510152025
4 Major48121620
3 Moderate3691215
2 Minor246810
1 Insignificant12345

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

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 IDDescriptionCovered By Test IDs
REQ‑SUB‑01User can purchase a monthly subscriptionSUB-001, SUB-009, SUB-010, SUB-012
REQ‑SUB‑02User can apply a promotional discountSUB-002, SUB-013
REQ‑SUB‑03Subscription can be upgraded/downgradedSUB-003, SUB-004
REQ‑SUB‑04Trial converts to paid subscriptionSUB-005, SUB-019
REQ‑SUB‑05System handles payment gateway errorsSUB-009, SUB-010, SUB-015, SUB-016
REQ‑SUB‑06Accessibility compliance (WCAG 2.1 AA)SUB-026
REQ‑SUB‑07Data‑privacy consent enforcementSUB-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:

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:

A common stack:

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:

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:

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:

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