Files
purchases-ios/Tests/StoreKitUnitTests/TestHelpers/StoreKitConfigTestCase+Extensions.swift
T
NachoSoto 2fff5b0b75 CI: also run tests on watchOS (#2340)
This is a follow up to #717.
In order to be able to reproduce #2338, which seems to be an issue only
on `watchOS`, it's important that we now run tests on `watchOS` as well.
We probably won't be able to fully replicate that issue since we don't
run tests on actual devices, so they'll still run on 64 bits. But this
way we might be able to prevent issues that are only reproducible on
`watchOS` in the future.

## Changes:
- Added CircleCI job
- Added lane
- Simplified `UnitTestsHostApp` definition using `SwiftUI`
- Increased _test only_ `watchOS` deployment target to 7.0 (this was
necessary to be able to have a common `@main` for the host app)
- Increased _test only_ `tvOS` deployment target to `14.0` (same as
above, plus we only run tvOS tests on the latest version anyway. We test
backwards compatibility through iOS already)
- Fixed test compilation on `watchOS`
- Disabled `HTTPClient` tests on `watchOS` (see
https://github.com/AliSoftware/OHHTTPStubs/issues/287)
- `StoreKitConfigTestCase` is only available from `watchOS 7.0` since
that's when `SKTestSession` was introduced
- Added `watchOS` support to `UnitTests` target
- Added `watchOS` support to `ReceiptParserTests` target
- Fixed `isFamilySharable` `watchOS` version checks for version 8.0
- Created new `CI-RevenueCat` Test Plan to only run unit tests and not
StoreKit tests on `watchOS`
2023-03-16 13:11:24 -07:00

133 lines
4.0 KiB
Swift

//
// Copyright RevenueCat Inc. All Rights Reserved.
//
// Licensed under the MIT License (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://opensource.org/licenses/MIT
//
// StoreKitConfigTestCase+Extensions.swift
//
// Created by Nacho Soto on 11/17/22.
import Nimble
@testable import RevenueCat
@preconcurrency import StoreKit
import XCTest
@available(iOS 15.0, tvOS 15.0, macOS 12.0, watchOS 8.0, *)
extension StoreKitConfigTestCase {
@MainActor
@discardableResult
func simulateAnyPurchase() async throws -> SK2Product {
let product = try await fetchSk2Product()
_ = try await product.purchase()
return product
}
@MainActor
func createTransactionWithPurchase() async throws -> Transaction {
let product = try await self.simulateAnyPurchase()
let transaction = try await XCTAsyncUnwrap(await product.latestTransaction)
switch transaction {
case let .verified(transaction):
return transaction
default:
XCTFail("Invalid transaction: \(transaction)")
fatalError("Unreachable")
}
}
@MainActor
func fetchSk2Product(_ productID: String = StoreKitConfigTestCase.productID) async throws -> SK2Product {
let products: [SK2Product] = try await StoreKit.Product.products(for: [productID])
return try XCTUnwrap(products.first)
}
@MainActor
@available(iOS 15.0, tvOS 15.0, watchOS 8.0, macOS 12.0, *)
func fetchSk2StoreProduct(_ productID: String = StoreKitConfigTestCase.productID) async throws -> SK2StoreProduct {
return SK2StoreProduct(sk2Product: try await self.fetchSk2Product(productID))
}
}
@available(iOS 14.0, tvOS 14.0, macOS 11.0, watchOS 7.0, *)
extension StoreKitConfigTestCase {
/// Updates `SKTestSession.storefront` and waits for `Storefront.current` to reflect the change
/// This is necessary because the change is aynchronous within `StoreKit`, and otherwise code that depends
/// on the change might not see it in time, resulting in race conditions and flaky tests.
func changeStorefront(
_ new: String,
file: FileString = #fileID,
line: UInt = #line
) async {
self.testSession.storefront = new
await asyncWait(
until: { await Storefront.currentStorefront?.countryCode == new },
timeout: .seconds(1),
pollInterval: .milliseconds(100),
description: "Storefront change not detected"
)
}
}
@available(iOS 14.0, tvOS 14.0, macOS 11.0, watchOS 7.0, *)
extension StoreKitConfigTestCase {
static let productID = "com.revenuecat.monthly_4.99.1_week_intro"
static let lifetimeProductID = "lifetime"
}
@available(iOS 14.0, tvOS 14.0, macOS 11.0, watchOS 7.0, *)
fileprivate extension StoreKitConfigTestCase {
enum Error: Swift.Error {
case noProductsFound
case multipleProductsFound
}
}
@available(iOS 14.0, tvOS 14.0, macOS 11.0, watchOS 7.0, *)
extension ProductsFetcherSK1 {
func product(withIdentifier identifier: String) async throws -> StoreProduct {
let products = try await self.products(withIdentifiers: Set([identifier]))
switch products.count {
case 0: throw StoreKitConfigTestCase.Error.noProductsFound
case 1: return StoreProduct.from(product: products.first!)
default: throw StoreKitConfigTestCase.Error.multipleProductsFound
}
}
}
@MainActor
@available(iOS 15.0, tvOS 15.0, watchOS 8.0, macOS 12.0, *)
extension ProductsFetcherSK2 {
func product(withIdentifier identifier: String) async throws -> StoreProduct {
let products = try await self.products(identifiers: Set([identifier]))
switch products.count {
case 0: throw StoreKitConfigTestCase.Error.noProductsFound
case 1: return StoreProduct.from(product: products.first!)
default: throw StoreKitConfigTestCase.Error.multipleProductsFound
}
}
}