4

前言

StoreKit 框架的第二次迭代是我在过去几年中应用程序中最重大的变化。最近版本的 StoreKit 框架已完全采用了 Swift 语言特性,如 async 和 await。本篇内容我们将讨论 StoreKitTest 框架,这不是 StoreKit 2 的一部分,但与之紧密耦合。

StoreKitTest 框架为我们提供了 SKTestSession 类型。使用 SKTestSession 类型的实例,我们可以购买应用内产品、管理交易、退款和过期订阅等。

创建一个 StoreKit Demo

我们从创建一个 StoreKit 相关功能的测试用例开始。我通常有一个称为 SettingsStore 的类型,它定义用户配置并处理应用内购买。我们将使用 StoreKitTest 框架通过测试来覆盖 SettingsStore 的应用内购买管理部分。

Copy code
@MainActor final class StoreKitTests: XCTestCase {
    func testProductPurchase() async throws {
        let session = try SKTestSession(configurationFileNamed: "SugarBot Food Calorie Counter")
        session.disableDialogs = true
        session.clearTransactions()
    }
}

如上例所示,我们初始化了 SKTestSession 类型的实例。然后,我们调用 clearTransactions 函数来删除我们可能从以前的启动中存储的所有交易。我们还关闭对话框以轻松自动化购买确认流程。

使用 SKTestSession

现在,我们可以使用我们的 SettingsStore 类型来购买产品并处理订阅状态。SKTestSession 类型还允许我们购买一个模拟应用外购买的产品。例如,可能是一个启用了家庭共享的已购买产品。

Copy code
@MainActor final class StoreKitTests: XCTestCase {
    var store: SettingsStore!
    
    override func setUp() {
        store = SettingsStore()
    }
    
    func testProductPurchase() async throws {
        let session = try SKTestSession(configurationFileNamed: "SugarBot Food Calorie Counter")
        session.disableDialogs = true
        session.clearTransactions()
        
        try await session.buyProduct(identifier: "annual")
        
        guard let product = try await Product.products(for: ["annual"]).first else {
            return XCTFail("Can't load products...")
        }
        
        let status = try await product.subscription?.status ?? []
        await store.processSubscriptionStatus(status)
        
        XCTAssertFalse(store.activeSubscriptions.isEmpty)
    }
}

如上例所示,我们使用 SKTestSession 类型的 buyProduct 函数来模拟购买。我们还可以使用 SKTestSession 类型的 expireSubscription 函数来过期进行中的订阅,并验证我们的应用程序如何处理这些数据。

Copy code
@MainActor final class StoreKitTests: XCTestCase {
    var store: SettingsStore!
    
    override func setUp() {
        store = SettingsStore()
    }
    
    func testExpiredProduct() async throws {
        let session = try SKTestSession(configurationFileNamed: "SugarBot Food Calorie Counter")
        session.disableDialogs = true
        session.clearTransactions()
        
        let transaction = try await session.buyProduct(identifier: "annual")
        
        let activeProducts = try await Product.products(for: ["annual"])
        let activeStatus = try await activeProducts.first?.subscription?.status ?? []
        await store.processSubscriptionStatus(activeStatus)
        
        XCTAssertFalse(store.activeSubscriptions.isEmpty)
        
        try session.expireSubscription(productIdentifier: "annual")
        
        let expiredProducts = try await Product.products(for: ["annual"])
        let expiredStatus = try await expiredProducts.first?.subscription?.status ?? []
        await store.processSubscriptionStatus(expiredStatus)
        
        XCTAssertTrue(store.activeSubscriptions.isEmpty)
    }
}

SKTestSession 类型还允许我们使用 refundTransaction 函数模拟产品退款。另一个令人兴奋的选项是测试应用程序对交易更新的反应。

Copy code
let transaction = try await session.buyProduct(identifier: "annual")
// 验证购买 ...
try session.refundTransaction(identifier: UInt(transaction.id))
// 验证退款 ...

askToBuyEnabled 属性

你还可以使用 askToBuyEnabled 属性来启用询问购买功能,然后使用 approveAskToBuyTransactiondeclineAskToBuyTransaction 函数来批准或拒绝购买。在这种情况下,交易应从挂起更改为成功。

Copy code
session.askToBuyEnabled = true

await store.purchase("annual")
// 验证购买 ...

let declined = store.pendingTrancations.first?.id ?? 0
try session.declineAskToBuyTransaction(identifier: UInt(declined.id))
// 验证购买 ...

await store.purchase("annual")
// 验证购买 ...

let approved = store.pendingTrancations.first?.id ?? 0
try session.approveAskToBuyTransaction(identifier: UInt(approved.id))
// 验证购买 ...

如上例所示,我们使用 SKTestSession 类型的实例来模拟询问购买,并验证我们的应用程序在购买被批准或拒绝时的行为。

总结

本文介绍了如何创建测试用例,然后详细说明了如何使用 SKTestSession 类型来模拟购买、退款和订阅过期等情况,并展示了如何测试应用程序对这些情况的处理。此外,还介绍了使用 askToBuyEnabled 属性启用询问购买功能的方法,并展示了如何验证应用程序对购买被批准或拒绝时的行为。通过这篇文章,读者可以了解如何使用 StoreKitTest 框架来验证应用程序处理应用内购买和用户流程的能力。


Swift社区
11.9k 声望3.8k 粉丝

我们希望做一个最专业最权威的 Swift 中文社区,我们希望更多的人学习和使用Swift。我们会分享以 Swift 实战、SwiftUI、Swift 基础为核心的技术干货,欢迎您的关注与支持。