I have followed the StoreKit test setup guide and it is worked in integrated test.
After that, I want to create unit test about SKPaymentTransactionObserver. I have write the unit test code like below.
import XCTest
import StoreKitTest
@available(iOS 14.0, *)
final class SimpleSKProductTransactionObserverTest: XCTestCase {
var sut: MockSKProductTransactionObserver!
var session: SKTestSession!
override func setUpWithError() throws {
session = try SKTestSession(configurationFileNamed: "ShopStoreKitConfig")
session.resetToDefaultState()
session.disableDialogs = true
session.clearTransactions()
sut = .init()
SKPaymentQueue.default().add(sut)
}
override func tearDownWithError() throws {
SKPaymentQueue.default().remove(sut)
sut = nil
session = nil
}
func testPaymentQueue() throws {
let productId = "prod_sale1_battery_1_day"
try session.buyProduct(productIdentifier: productId)
XCTAssertTrue(sut.isPaymentQueueCalled)
XCTAssertEqual(sut.previousTransactions.first?.payment.productIdentifier, productId)
}
}
class MockSKProductTransactionObserver: NSObject, SKPaymentTransactionObserver {
var previousTransactions = [SKPaymentTransaction]()
var isPaymentQueueCalled = false
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
isPaymentQueueCalled = true
previousTransactions = transactions
}
}
However, it is failed the assert codes.
/Users/jooyoulkim/git/rp_ios/PickaTests/SimpleSKProductTransactionObserverTest.swift:39 testPaymentQueue(): XCTAssertTrue failed
/Users/jooyoulkim/git/rp_ios/PickaTests/SimpleSKProductTransactionObserverTest.swift:40 testPaymentQueue(): XCTAssertEqual failed: ("nil") is not equal to ("Optional("prod_sale1_battery_1_day")")
The other side, I can see that SKPaymentTransactionObserver added in AppDelegate is worked normally.
So, the question is why observer is not added. Please tell me if have any idea about it. Thanks.
The problem is not that an observer cannot be added but an observer is worked async.
So, it should be written by using expectation.
For using expectation's fulfill, it need to callback after observer is updated or removed transaction.
class ProxySKProductTransactionObserver: NSObject, SKPaymentTransactionObserver {
var origin: SKPaymentTransactionObserver
var updatedTransactionsPostCallback: ((SKPaymentQueue, [SKPaymentTransaction]) -> Void)?
var removedTransactionsPostCallback: ((SKPaymentQueue, [SKPaymentTransaction]) -> Void)?
init(_ origin: SKPaymentTransactionObserver) {
self.origin = origin
}
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
origin.paymentQueue(queue, updatedTransactions: transactions)
updatedTransactionsPostCallback?(queue, transactions)
}
func paymentQueue(_ queue: SKPaymentQueue, removedTransactions transactions: [SKPaymentTransaction]) {
origin.paymentQueue?(queue, removedTransactions: transactions)
removedTransactionsPostCallback?(queue, transactions)
}
}
After using expectation, test is passed
import XCTest
import StoreKitTest
@available(iOS 14.0, *)
final class SimpleSKProductTransactionObserverTest: XCTestCase {
var sut: MockSKProductTransactionObserver!
var proxy: ProxySKProductTransactionObserver!
var session: SKTestSession!
override func setUpWithError() throws {
session = try SKTestSession(configurationFileNamed: "ShopStoreKitConfig")
session.resetToDefaultState()
session.disableDialogs = true
session.clearTransactions()
sut = .init()
proxy = .init(sut)
SKPaymentQueue.default().add(proxy)
}
override func tearDownWithError() throws {
SKPaymentQueue.default().remove(proxy)
proxy = nil
sut = nil
session = nil
}
func testPaymentQueue() throws {
let productId = "prod_sale1_battery_1_day"
let expectation = self.expectation(description: "updatedTransaction is called")
proxy.updatedTransactionsPostCallback = { _, _ in
expectation.fulfill()
}
try session.buyProduct(productIdentifier: productId)
waitForExpectations(timeout: 5)
XCTAssertTrue(sut.isPaymentQueueCalled)
XCTAssertEqual(sut.previousTransactions.first?.payment.productIdentifier, productId)
}
}
class MockSKProductTransactionObserver: NSObject, SKPaymentTransactionObserver {
var previousTransactions = [SKPaymentTransaction]()
var isPaymentQueueCalled = false
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
isPaymentQueueCalled = true
previousTransactions = transactions
}
}