前语

StoreKit 结构的第2次迭代是我在过去几年中运用程序中最重大的改变。最近版本的 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 结构来验证运用程序处理运用内购买和用户流程的能力。