StoreKit Testing in Xcode 12: Getting Started
Learn to use the new StoreKit testing framework to exercise your in-app purchases right inside Xcode. By Saleh Albuga.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
StoreKit Testing in Xcode 12: Getting Started
30 mins
- Getting Started
- Creating StoreKit Configuration Files
- Adding Salad Recipes
- Adding Healthy Coins
- Adding Newsletter Subscriptions
- Testing in Xcode During Development
- Testing Non-Consumables
- Simulating Ask to Buy
- Testing Consumables
- Simulating Other Storefronts
- Testing Subscriptions
- Deleting Transactions
- Deleting a Subscription
- Simulating Refunds
- Comparing Testing in Xcode to The Sandbox Environment
- Automating StoreKitTest Tests
- Testing Receipt Validation
- Starting Automated Testing
- Testing First Time Purchases
- Testing Interrupted Purchases
- Testing Subscriptions
- Where to Go From Here?
Starting Automated Testing
Open GreenBarTests.swift and add the following line below import XCTest
:
import StoreKitTest
@testable import GreenBar
This code imports the StoreKitTest framework and your app so you can run tests against it.
Next, add the following test method inside GreenBarTests
:
func testReceiptValidation() throws {
// 1
let session = try SKTestSession(configurationFileNamed: "RecipesAndCoins")
session.resetToDefaultState()
session.disableDialogs = true
// 2
let identifier = GreenBarContent.tartCherrySalad
let expectation = XCTestExpectation(description: "Buy Content")
// 3
GreenBarContent.store.requestProductsAndBuy(
productIdentifier: identifier
) { _ in
// 4
let receipt = Receipt()
if let receiptStatus = receipt.receiptStatus {
let validationSucceeded = receiptStatus == .validationSuccess
XCTAssertTrue(validationSucceeded)
} else {
XCTFail("Receipt Validation failed")
}
expectation.fulfill()
}
wait(for: [expectation], timeout: 60.0)
}
Here’s a code breakdown:
- First, you create a
SKTestSession
and configure it with the ReceiptsAndCoins configuration file. You also reset the session to its default state and disable any UI dialogs that might trigger during the session. - Next, you establish the product you want to test buying by using the Tart Cherry Salad’s product ID. You also create an expectation you’ll wait for because the test will run asynchronously.
- Then, you execute a purchase of the Tart Cherry Salad using
requestProductsAndBuy(productIdentifier:completionHandler:)
fromIAPHelper
instancestore
. It talks to StoreKit, persists the purchase and updates the app content accordingly. - Finally, after the purchase, you check the receipt validating the purchase was successful.
Those two actions serve the same purpose: Telling Xcode which set of products you want to load into the test environment. In fact, all the StoreKit configuration settings you saw in Editor are configurable in code using SKTestSession
‘s properties.
SKTestSession
‘s initializer.
Those two actions serve the same purpose: Telling Xcode which set of products you want to load into the test environment. In fact, all the StoreKit configuration settings you saw in Editor are configurable in code using SKTestSession
‘s properties.
Run your test by clicking the diamond test indicator located to the left of testReceiptValidation()
. Alternatively, you can also run the test by pressing Command-U. Your test passes!
Next, you’ll test first time purchases.
Testing First Time Purchases
It’s important to test first-time purchases to ensure sure your app correctly handles users purchasing new content.
Replace testReceiptValidation()
with:
func testNonConsumablePurchase() throws {
// 1
let session = try SKTestSession(configurationFileNamed: "RecipesAndCoins")
session.resetToDefaultState()
session.disableDialogs = true
session.clearTransactions()
// 2
let identifier = GreenBarContent.caesarSalad
let expectation = XCTestExpectation(description: "Buy Content")
GreenBarContent.store.requestProductsAndBuy(
productIdentifier: identifier
) { _ in
// 3
let contentAvailable = GreenBarContent.store.receiptContains(identifier)
let contentSaved = GreenBarContent.store.isProductPurchased(identifier)
XCTAssertTrue(
contentAvailable,
"Expected \(identifier) is present in receipt")
XCTAssertTrue(
contentSaved,
"Expected \(identifier) is stored in PurchasedProducts")
expectation.fulfill()
}
wait(for: [expectation], timeout: 60.0)
}
This test is similar to the last. Here’s what your code does:
- Like previous test, this test starts by preparing a
SKTestSession
. However, you’re testing first time purchases so you callclearTransactions()
to delete all past transactions and start from scratch. - You set
identifier
to the Caesar Salad product ID. - Finally, you make sure the receipt contains the product ID. You also make sure the product ID appears in your purchased products list.
Run your test by pressing Command-U. Like before, the green diamond with a checkmark indicates your test passes!
Now, you’ll test interrupted purchases.
Testing Interrupted Purchases
Interrupted purchases are transactions that ask users to perform an action related to their Apple ID before completing. For example, a user might have to update payment information.
To test an interrupted purchase, replace testNonConsumablePurchase()
with:
func testInterruptedPurchase() throws {
let session = try SKTestSession(configurationFileNamed: "RecipesAndCoins")
session.resetToDefaultState()
session.disableDialogs = true
// 1
session.interruptedPurchasesEnabled = true
session.clearTransactions()
let identifier = GreenBarContent.healthyTacoSalad
let expectation = XCTestExpectation(description: "Buy Content")
GreenBarContent.store.requestProductsAndBuy(
productIdentifier: identifier
) { _ in
let contentAvailable = GreenBarContent.store.receiptContains(identifier)
let contentSaved = GreenBarContent.store.isProductPurchased(identifier)
// 2
XCTAssertFalse(
contentAvailable,
"Expected \(identifier) is not present in receipt")
XCTAssertFalse(
contentSaved,
"Expected \(identifier) is not stored in PurchasedProducts")
expectation.fulfill()
}
wait(for: [expectation], timeout: 60.0)
}
This test is almost identical to the previous one, except for two key differences:
- You set
SKTestSession
‘sinterruptedPurchasesEnabled
totrue
to simulate an interrupted purchase. - Because it’s an interrupted purchase you expect the product isn’t in the receipt or your previous purchases.
Run your test with Command-U and watch for the green checkmark.
Finally, it’s time to test subscriptions.
Testing Subscriptions
Your final test exercises GreenBar’s ability to create and expire subscriptions. Replace testInterruptedPurchase()
with:
func testSubscription() throws {
// 1
let session = try SKTestSession(configurationFileNamed: "Subscriptions")
session.resetToDefaultState()
session.disableDialogs = true
session.clearTransactions()
// 2
let identifier = GreenBarContent.greenTimes
let expectation = XCTestExpectation(description: "Buy Content")
GreenBarContent.store.requestProductsAndBuy(
productIdentifier: identifier
) { _ in
// 3
let contentAvailable = GreenBarContent.store.receiptContains(identifier)
var contentSaved = GreenBarContent.store.isProductPurchased(identifier)
XCTAssertTrue(
contentAvailable,
"Expected \(identifier) is present in receipt")
XCTAssertTrue(
contentSaved,
"Expected \(identifier) is stored in PurchasedProducts")
// 4
var hasExpired = GreenBarContent.store.checkSubscriptionExpiry(identifier)
XCTAssertFalse(hasExpired, "Expected \(identifier) has not expired")
// 5
try? session.expireSubscription(productIdentifier: identifier)
hasExpired = GreenBarContent.store.checkSubscriptionExpiry(identifier)
contentSaved = GreenBarContent.store.isProductPurchased(identifier)
XCTAssertTrue(hasExpired, "Expected \(identifier) has expired")
XCTAssertFalse(
contentSaved,
"Expected \(identifier) is not stored in PurchasedProducts")
expectation.fulfill()
}
wait(for: [expectation], timeout: 90.0)
}
subscriptionExpirationDate
does not change in the local receipt after calling expireSubscription(productIdentifier:)
in later iOS versions. This appears still to be a problem in iOS 14.4.
This test looks a little longer but it’s simple! Here’s how it works:
- First, you create a new
SKTestSession
. However, this time you initialize it with your Subscriptions. - Next, you set the product you want to purchase to the Green Times newsletter product ID.
- After you purchase the subscription, you check if the receipt shows the Green Times newsletter ID. You also check if the newsletter is available in your purchased products.
- Then, you make sure the subscription you set up hasn’t expired.
- Last, you expire the subscription and make sure its no longer in your purchased products.
Run your last test and see that it passes.