rc-testing
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseTesting RevenueCat on Android
在Android上测试RevenueCat
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 interface makes mockable, and a small GitHub Actions job runs unit tests on every push.
BillingServicePurchasesFull source: see the full chapter on revenuecat.com.
使用此技能可为RevenueCat Android集成搭建快速测试循环。Test Store可替代Google Play沙箱完成大部分工作,接口让可被模拟,一个轻量的GitHub Actions任务会在每次代码推送时运行单元测试。
BillingServicePurchases完整源码:查看revenuecat.com上的完整章节。
Phase 1: Discovery
阶段1:现状调研
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? | | A |
| Release key separate? | | A |
| Purchases wrapped? | | An interface that hides |
| CI present? | | A job running |
| Secret wired? | GitHub repo settings, workflow | |
| Test library? | | |
If the project has none of these, start from scratch in Phase 3. If some exist, only fill the gaps.
在进行任何修改前,先了解现有配置。用文件路径或“无”回答每个问题。
| 检查项 | 查看位置 | 预期结果 |
|---|---|---|
| 是否配置了测试API密钥? | | debug构建中 |
| 是否区分发布密钥? | | 使用 |
| 是否封装了Purchases? | | 存在隐藏 |
| 是否有CI配置? | | 存在运行 |
| 是否配置了密钥? | GitHub仓库设置、工作流 | 从 |
| 是否引入测试库? | | 包含 |
如果项目完全没有上述配置,从阶段3开始从头搭建。如果已有部分配置,仅补充缺失项即可。
Phase 2: Plan
阶段2:测试规划
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 |
| 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 before coding. If the app ships subscriptions, keep one Sandbox pass in the pre-ship checklist even if the Test Store covers daily iteration.
tasks/todo.md根据场景选择测试路径。默认使用Test Store;仅当需要Test Store无法模拟的商店行为时,才退而使用Google Play沙箱。
| 场景 | 使用方案 |
|---|---|
| 成功、失败、取消流程 | Test Store |
| ViewModel单元测试 | 针对 |
| 每次推送触发CI | Test Store + 单元测试 |
| 订阅续订周期 | Google Play沙箱 |
| 待处理购买(家长审批) | Google Play沙箱 |
| 完整端到端支付 | Google Play沙箱 |
在编码前,将规划写成简短的 checklist 存入。如果应用包含订阅功能,即使Test Store覆盖了日常迭代,预发布检查清单中也要保留一次沙箱验证流程。
tasks/todo.mdPhase 3: Execute
阶段3:执行搭建
Step 1: Generate the test API key
步骤1:生成测试API密钥
In the RevenueCat dashboard, open your app, go to Apps & providers, then Create Test Store. Copy the key. Do not commit it. Put it in or a CI secret.
test_...local.properties在RevenueCat控制台中打开你的应用,进入Apps & providers,然后点击Create Test Store。复制格式的密钥。请勿提交该密钥,将其存入或CI密钥中。
test_...local.propertiesStep 2: Wire the key into debug builds
步骤2:将密钥接入debug构建
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\"")kotlin
// build.gradle.kts
android {
buildTypes {
debug {
buildConfigField("String", "RC_API_KEY", "\"test_YOUR_KEY\"")
}
release {
buildConfigField("String", "RC_API_KEY", "\"goog_YOUR_KEY\"")
}
}
}对于CI环境,从环境变量读取密钥,避免密钥存入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
步骤3:用构建配置密钥初始化Purchases
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()
)
}
}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
步骤4:触发Test Store对话框
Run the app in debug and call from your paywall. A dialog appears with Success, Fail, and Cancel options. Each choice resolves the same way production would: Success returns a with active entitlements, Fail throws with a payment error, Cancel throws with . Walk every branch of your error handling to verify UI states.
awaitPurchase()PurchaseResultPurchasesTransactionExceptionuserCancelled = true以debug模式运行应用,从付费墙调用。此时会弹出包含成功、失败、取消选项的对话框。每个选项的处理逻辑与生产环境一致:成功返回带有有效权益的,失败抛出带有支付错误的,取消抛出的异常。遍历所有错误处理分支,验证UI状态是否正确。
awaitPurchase()PurchaseResultPurchasesTransactionExceptionuserCancelled = trueStep 5: Wrap Purchases in a BillingService
步骤5:用BillingService封装Purchases
The singleton is not mockable. A thin interface lets you inject a fake in tests and keeps ViewModels free of SDK types.
Purchaseskotlin
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()
}Purchaseskotlin
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
步骤6:编写mockk单元测试
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.
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)
}
}为每个分支编写测试:成功授予权限、失败显示错误、取消保持空闲状态。
Step 7: Add a minimal GitHub Actions job
步骤7:添加轻量GitHub Actions任务
yaml
undefinedyaml
undefined.github/workflows/test.yml
.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.
---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
将`test_...`格式的密钥存储为仓库密钥`RC_TEST_STORE_KEY`。该任务无需设备或Google账号即可运行。
---Verification
验证
After each purchase, open the RevenueCat dashboard, go to Customers, pick the user, and confirm the purchase, the entitlement, and the JSON. The Events tab shows webhooks fired for the test purchase.
CustomerInfo每次测试购买后,打开RevenueCat控制台,进入Customers,选择用户,确认购买记录、权益以及 JSON。Events标签页会显示测试购买触发的Webhook事件。
CustomerInfoPre-Ship Checklist
预发布检查清单
- gated on
Purchases.logLevel = LogLevel.DEBUGBuildConfig.DEBUG - Release build uses the key, not the
goog_keytest_ - key is not committed (env var or
test_)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
- 仅在
Purchases.logLevel = LogLevel.DEBUG下启用BuildConfig.DEBUG - 发布构建使用格式密钥,而非
goog_格式test_ - 格式密钥未提交至git(使用环境变量或
test_)local.properties - 通过Test Store对话框验证了成功、失败、取消流程
- 至少在Google Play沙箱中验证了一次完整端到端流程
- Webhook端点能接收沙箱购买事件