Skip to main content
skill CX Codex

rc-testing — Claude Code skill: what it does, how to install

Configures RevenueCat Test Store on Android, wraps the Purchases SDK for MockK unit testing, and sets up headless CI checks in GitHub Actions.

Install into Claude Code · Skills CLI (Vercel) All methods and platforms ↓
npx skills add RevenueCat/ai-toolkit --skill rc-testing -a claude-code
Open on GitHub

What rc-testing does

Testing Google Play billing during daily development iterations is notoriously slow when relying exclusively on the regular sandbox. This skill implements the RevenueCat Test Store using a test_ API key. Running debug builds presents a modal with Success, Fail, and Cancel choices, letting you verify entitlement activation and UI error states directly on your device.

The Purchases singleton resists direct unit testing. Because of this limitation, the workflow wraps SDK calls inside a lean BillingService interface. ViewModels depend only on this abstraction. That makes state transitions testable with MockK and kotlinx-coroutines-test, while a minimal GitHub Actions job executes ./gradlew testDebugUnitTest on headless Ubuntu runners using an environment secret.

The Test Store does not simulate recurring subscription renewals, parental approval flows, or end-to-end checkout steps. Those specific billing behaviors still demand verification inside Google Play Sandbox prior to shipping.

What it helps with

  • simulate purchase success, failure, and cancellation paths without sandbox delays
  • wrap the Purchases singleton inside a testable BillingService interface
  • write unit tests for ViewModels using MockK and coroutines
  • run Android billing tests in GitHub Actions without an emulator

Example prompts for the agent

  • Help me set up RevenueCat Test Store in build.gradle.kts for debug builds
  • Wrap Purchases into a BillingService interface and write a MockK test for my paywall
  • Create a GitHub Actions workflow to run billing unit tests with test keys

How to install rc-testing

1Skills CLI (Vercel)

npx skills add RevenueCat/ai-toolkit --skill rc-testing -a claude-code

2Manually: put the files in place

~/.claude/skills/rc-testing/SKILL.md

Copy the whole skill folder; for one project use .claude/skills/rc-testing/

Step by step, with commands for each platform: How to install skills in Claude Code: five ways

Questions about rc-testing

How to install rc-testing in Claude Code?

Run npx skills add RevenueCat/ai-toolkit --skill rc-testing -a claude-code in your terminal.

What does rc-testing do?

It guides developers through testing RevenueCat on Android using Test Store dialogs, isolating SDK calls behind interfaces for MockK, and setting up CI workflows.

When is Google Play Sandbox still necessary?

You still need Google Play Sandbox to verify subscription renewal cycles, pending parental purchases, and full checkout flows before release.

Source text

SKILL.md · MIT
---
name: rc-testing
description: Use this skill when testing a RevenueCat Android integration. Covers the RevenueCat Test Store (test_ API key prefix, in dialog Success/Fail/Cancel choice), mockk based unit testing with an interface wrapper around Purchases, and a GitHub Actions CI pattern. Avoids Google Play sandbox for day to day test iteration.
license: Apache-2.0; see LICENSE
metadata:
  author: RevenueCat
  source: revenuecat-handbook chapter 16
  keywords:
  - android
  - revenuecat
  - testing
  - test-store
  - mockk
  - github-actions
---

# Testing RevenueCat on Android

Use this skill to stand up a fast test loop for a RevenueCat Android integration. The Test Store replaces the Google Play sandbox for most work, a `BillingService` interface makes `Purchases` mockable, and a small GitHub Actions job runs unit tests on every push.

Full source: see the [full chapter on revenuecat.com](https://www.revenuecat.com/guides/revenuecat-android-sdk/testing).

---

## Phase 1: Discovery

Before changing anything, learn what already exists. Answer each question with a file or a "no".

| Check | Where to look | What you want |
|---|---|---|
| Test API key configured? | `app/build.gradle.kts`, `local.properties`, env | A `test_...` value bound to `BuildConfig.RC_API_KEY` in debug |
| Release key separate? | `build.gradle.kts` release block | A `goog_...` key, never `test_...` |
| Purchases wrapped? | `app/src/main/java/**/*Billing*.kt` | An interface that hides `Purchases.sharedInstance` |
| CI present? | `.github/workflows/*.yml` | A job running `./gradlew testDebugUnitTest` |
| Secret wired? | GitHub repo settings, workflow `env:` | `RC_TEST_STORE_KEY` referenced from `secrets` |
| Test library? | `app/build.gradle.kts` dependencies | `io.mockk:mockk` and `kotlinx-coroutines-test` |

If the project has none of these, start from scratch in Phase 3. If some exist, only fill the gaps.

---

## Phase 2: Plan

Pick the test path per scenario. The Test Store is the default; fall back to Google Play sandbox only when you need store behavior the Test Store does not simulate.

| Scenario | Use |
|---|---|
| Success, failure, cancel paths | Test Store |
| Unit tests of ViewModels | mockk against `BillingService` |
| CI on every push | Test Store + unit tests |
| Subscription renewal cycles | Google Play Sandbox |
| Pending purchase (parental approval) | Google Play Sandbox |
| Full end-to-end payment | Google Play Sandbox |

Write the plan as a short checklist in `tasks/todo.md` before coding. If the app ships subscriptions, keep one Sandbox pass in the pre-ship checklist even if the Test Store covers daily iteration.

---

## Phase 3: Execute

### Step 1: Generate the test API key

In the RevenueCat dashboard, open your app, go to **Apps & providers**, then **Create Test Store**. Copy the `test_...` key. Do not commit it. Put it in `local.properties` or a CI secret.

### Step 2: Wire the key into debug builds

```kotlin
// build.gradle.kts
android {
    buildTypes {
        debug {
            buildConfigField("String", "RC_API_KEY", "\"test_YOUR_KEY\"")
        }
        release {
            buildConfigField("String", "RC_API_KEY", "\"goog_YOUR_KEY\"")
        }
    }
}
```

For CI, read from env so the key never lands in git:

```kotlin
// build.gradle.kts
val testKey = System.getenv("RC_TEST_STORE_KEY") ?: "test_placeholder"
buildConfigField("String", "RC_API_KEY", "\"$testKey\"")
```

### Step 3: Configure Purchases with the build config key

```kotlin
class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        if (BuildConfig.DEBUG) Purchases.logLevel = LogLevel.DEBUG
        Purchases.configure(
            PurchasesConfiguration.Builder(this, BuildConfig.RC_API_KEY).build()
        )
    }
}
```

### Step 4: Trigger the Test Store dialog

Run the app in debug and call `awaitPurchase()` from your paywall. A dialog appears with Success, Fail, and Cancel options. Each choice resolves the same way production would: Success returns a `PurchaseResult` with active entitlements, Fail throws `PurchasesTransactionException` with a payment error, Cancel throws with `userCancelled = true`. Walk every branch of your error handling to verify UI states.

### Step 5: Wrap Purchases in a BillingService

The `Purchases` singleton is not mockable. A thin interface lets you inject a fake in tests and keeps ViewModels free of SDK types.

```kotlin
interface BillingService {
    suspend fun getOfferings(): Offerings
    suspend fun purchase(activity: Activity, pkg: Package): CustomerInfo
    suspend fun getCustomerInfo(): CustomerInfo
}
```

```kotlin
class RevenueCatBillingService : BillingService {
    override suspend fun getOfferings() =
        Purchases.sharedInstance.awaitOfferings()

    override suspend fun purchase(activity: Activity, pkg: Package): CustomerInfo =
        Purchases.sharedInstance.awaitPurchase(
            PurchaseParams.Builder(activity, pkg).build()
        ).customerInfo

    override suspend fun getCustomerInfo() =
        Purchases.sharedInstance.awaitCustomerInfo()
}
```

### Step 6: Write mockk unit tests

```kotlin
class PaywallViewModelTest {
    private val billing = mockk<BillingService>()
    private val viewModel = PaywallViewModel(billing)

    @Test
    fun `purchase success grants access`() = runTest {
        val info = mockk<CustomerInfo> {
            every { entitlements["pro_access"]?.isActive } returns true
        }
        coEvery { billing.purchase(any(), any()) } returns info
        viewModel.purchase(mockActivity, mockPackage)
        assertTrue(viewModel.state.value is PaywallState.Success)
    }
}
```

Add one test per branch: success grants access, failure shows error, cancel stays idle.

### Step 7: Add a minimal GitHub Actions job

```yaml
# .github/workflows/test.yml
name: test
on: [push, pull_request]
jobs:
  unit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with:
          distribution: temurin
          java-version: 17
      - name: Run tests
        env:
          RC_TEST_STORE_KEY: ${{ secrets.RC_TEST_STORE_KEY }}
        run: ./gradlew testDebugUnitTest
```

Store the `test_...` key as the `RC_TEST_STORE_KEY` repository secret. The job runs without a device or a Google account.

---

## Verification

After each purchase, open the RevenueCat dashboard, go to **Customers**, pick the user, and confirm the purchase, the entitlement, and the `CustomerInfo` JSON. The **Events** tab shows webhooks fired for the test purchase.

## Pre-Ship Checklist

- [ ] `Purchases.logLevel = LogLevel.DEBUG` gated on `BuildConfig.DEBUG`
- [ ] Release build uses the `goog_` key, not the `test_` key
- [ ] `test_` key is not committed (env var or `local.properties`)
- [ ] Success, failure, and cancel paths exercised through the Test Store dialog
- [ ] At least one end-to-end flow verified in Google Play Sandbox
- [ ] Webhook endpoint receives events for sandbox purchases

## References

- [Full chapter](https://www.revenuecat.com/guides/revenuecat-android-sdk/testing)

© RevenueCat · MIT · RevenueCat/ai-toolkit

Similar skills

All skills for Testing and QA →

From the same set33

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor