StoreKitTestで購入キャンセル処理のテストを書く

## 概要

StoreKitTestを使って、課金キャンセル処理のユニットテストを書きたかった。
最初利用しようと思っていた`SKTestSession`のプロパティがDeperecatedになっていたので、別の方法で実装しようとした。
→少し詰まったので、その備忘録

## 実装（Deperecated）

課金処理を管理する`PurchaseManager`クラスでは、以下のように、支払い処理の結果に応じてStateを切り替えているので、caseが`.userCancelled`になった時のstateをチェックするテストを書く必要がある。
```swift

func purchase(_ product: Product) async {
        do {
            let result = try await product.purchase()
            
            switch result {
            case let .success(.verified(transaction)):
                await transaction.finish()
                await self.updatePurchasedProducts()
                state = .success
            case .success(.unverified(_, _)), .pending:
                state = .idle
                break
            case .userCancelled:
                state = .failure(.canceled)
                break
            @unknown default:
                state = .failure(.unknown)
                break
            }
        } catch {
            state = .failure(.unknown)
     }   
 }

```

そこで購入キャンセル時のテストケースを以下のように書いた。

```swift

final class PurchaseManagerTest: XCTestCase {
    
    var session: SKTestSession!
    var purchaseManager: PurchaseManager!

    // setUpは記載省略

    func testPurchaseCancellation() async throws {
        // 購入キャンセルの設定
        session.failTransactionsEnabled = true // deperecated
        session.failureError = SKError.paymentCancelled //deperecated
        
        try await purchaseManager.loadProducts()
        let product = purchaseManager.product
        
        await purchaseManager.purchase(purchaseManager.product)
        
        // 購入がキャンセルされたことを確認
        XCTAssertEqual(purchaseManager.state, .failure(.canceled))
    }

}

```
が、sessionをキャンセル状態に設定するところで、iOS17以降でdeperecatedになっている旨の警告が出た。
（[ドキュメント](https://developer.apple.com/documentation/storekittest/sktestsession)）

![スクリーンショット 2024-09-27 0.37.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/66928/482b4635-20e0-b27d-f2ae-bd7c1b09a1ea.png)

そもそもテストも通らなかったので、`SKError.paymentCancelled`を指定しているのが間違っている様子。（単純に理解が浅かった）

と言うわけで、別のコードに書き換えを実施。

## 実装（修正版）

`simulatedError(forAPI:)`を使えと警告が出ていたが、課金キャンセル処理を設定したい場合使うのは`setSimulatedError<API>(API.Failure?, forAPI: API)`の方だった。

[WWDC2023の動画に解説があった](https://developer.apple.com/jp/videos/play/wwdc2023/10140/?time=1308)のでそちらを元に修正。

修正後のテストケースはこちら。

```swift

final class PurchaseManagerTest: XCTestCase {
    
    var session: SKTestSession!
    var purchaseManager: PurchaseManager!

    // setUpは記載省略

    func testPurchaseCancellation() async throws {
        // 購入キャンセルの設定
        try await session.setSimulatedError(
                StoreKitPurchaseAPI.Failure.generic(.userCancelled),
                forAPI: StoreKitPurchaseAPI.purchase
        )
        
        try await purchaseManager.loadProducts()
        let product = purchaseManager.product
        
        await purchaseManager.purchase(purchaseManager.product)
        
        // 購入がキャンセルされたことを確認
        XCTAssertEqual(purchaseManager.state, .failure(.canceled))
    }

}

```

無事テストも通るようになり、めでたしめでたし。

genericがピンと来なかったので、型を明示的に書いているけれど、もちろん以下のように型省略でも書けます。

```swift
try await session.setSimulatedError(.generic(.userCancelled), forAPI: .purchase)
```

## 終わりに
やはり公式を見るのが一番だったのだけれど、ドキュメントは説明これしか書いていなかったので、もうちょっとリファレンス充実させてほしい気もする...
![スクリーンショット 2024-09-27 1.10.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/66928/8e8f82c5-5221-1dd5-5d27-23f309b3f763.png)

わかれば簡単だったが、意外と参考資料がなくて少し詰まったところなので、本記事が参考になれば幸いです。