The ultimate guide to iOS subscription testing
Find and fix bugs so you don’t lose money due to issues in your subscription code.
Testing App Store subscriptions is extremely important, but it’s also hard to do well. Apple’s subscription-related documentation is… shall we say… lacking, and Apple doesn’t provide many testing resources. So we put together this guide to fill in some of the cracks.
We’ll update this post over time as Apple makes changes to subscriptions and we figure out better ways to test.
Looking for a guide to Android? Check out our Android subscription testing guide >>
iOS testing environments
There are three different testing environments you can use to test your iOS app at different stages of the development and release cycle: sandbox (developer builds), TestFlight (the production sandbox), and production (the App Store). Each one of these environments behaves a little differently, and ideally you should test in all three before releasing your app.
iOS testing in sandbox
The developer sandbox is your first line of defence. Make sure you understand its quirks and limitations during development to save time when you move on to production testing.
To test in the developer sandbox, the app has to be built as a developer build in Xcode. Developers often use this to quickly test on-device during the development process, but you can also provision devices and distribute developer builds to QA and other internal testers without having to go through TestFlight and their beta app review.
To prevent apps from being distributed widely outside the App Store, Apple limits device provisioning to 100 per device type (iPhone, iPad, Apple Watch, Apple TV, and Mac) for a total of 500 devices.
iOS testing in the developer sandbox requires a sandbox account. Apple has good documentation on this — hopefully it will stay up to date!
To sign in with a new sandbox account for the first time, you must attempt to make a purchase in a developer build of your app. Once you have logged in to a sandbox account using this method, you can then go to the Settings app, tap iTunes & App Store, then scroll to the bottom to the Sandbox Account section.
Here, you can log in and out of different sandbox accounts for testing. If you accidentally use a sandbox account on the production App Store, that account will no longer work in the sandbox. When in doubt, create a fresh sandbox account and retest.
(The Sandbox Account section of the Settings app was introduced in iOS 12. If you’re testing on iOS 11 or earlier, you’ll need to sign out of your production App Store account, then sign in with a test account when prompted within the app.)
Your device may repeatedly ask you to sign in to your sandbox account when attempting to test a purchase. This is normal, and you should sign in each time the dialog appears. Eventually iOS will be satisfied and let the purchase go through. We haven’t noticed this happen in production — it’s just a quirk of the sandbox environment.
A note about reliability
The developer sandbox is notoriously unreliable. (FYI, RevenueCat has built a convenient dashboard where you can check the sandbox environment’s uptime.) It’s unclear whether Apple intentionally degrades performance of the developer sandbox to mimic issues that might arise in production, or if Apple uses the developer sandbox as their own sandbox and frequently breaks it by accident.
Either way, keep in mind that even if your code is working as expected, things will sometimes go awry with the developer sandbox. When this happens, it’s a great opportunity to look for ways to handle unexpected errors.
That said, never assume that an issue will magically fix itself when you launch in production. There are certain known sandbox quirks that are documented in this guide, but you should still be able to fully test purchases end to end in the sandbox before shipping your app.
Subscription renewal rates in the developer sandbox
Subscription and trial lengths are significantly shortened in the sandbox for testing purposes. This allows developers to quickly and repeatedly test purchases, renewals, and expirations.
App Store Duration | Sandbox Duration |
3 days | 2 minutes |
1 week | 3 minutes |
1 month | 5 minutes |
2 months | 10 minutes |
3 months | 15 minutes |
6 months | 30 minutes |
1 year | 1 hour |
(The three-day duration isn’t documented anywhere but can be found by looking at sandbox transactions.)
You can change the renewal rate in your sandbox test account’s settings in App Store Connect.
Subscriptions will automatically renew up to 12 times per account, but the actual number of renewals is random. After at most 12 renewals, a subscription will automatically stop renewing.
Renewals happen automatically, whether the app is open or not (just like renewals on the App Store). Unlike on the App Store, there’s no option to unsubscribe or get a refund, so there’s no way to directly test those scenarios. There’s also no way to test subscription management.
Each automatic renewal is added to the payment queue. The transaction (or transactions, depending on how much time has passed) is processed the next time the app is opened. Make sure you close the app and reopen it to see the updated receipt. If you’re refreshing the receipts server-side, these additional transactions should be visible in the receipt.
Testing procedures
Renewals and subscription expiration
- Sign up for a monthly subscription.
- Close the app and set a 20-minute timer.
- After 20 minutes, launch the app again to make sure your app is still in the subscribed state.
- Close the app and set another 20-minute timer.
- After 20 more minutes (approx. 40 minutes since originally purchasing the subscription), launch the app again. It should now revert to the unsubscribed state and allow the user to resubscribe.
Restoring a purchase after expiration
- Sign up for a monthly subscription.
- Close the app and wait 35 to 40 minutes.
- Launch the app (it should revert to the unsubscribed state).
- Tap the Restore Purchases button.
- No active subscription should be found, and the user should be shown a message saying that.
Restoring a purchase during an active subscription
One big caveat with iOS testing in the sandbox environment is that there is no receipt file on the device until a purchase is made. This differs from the production sandbox and production environments, where a receipt file is generated at the time of installation. To fully test restoring a purchase in the sandbox, you need to add a button, gesture, or some other means to revert the app to the unsubscribed state.
- Sign up for a monthly subscription.
- Use a button or gesture to revert the app to the unsubscribed state.
- Tap the Restore Purchases button.
- If you do this before the 35-minute subscription cycle is over, an active subscription should be found, and the app should change to the subscribed state.
Restoring purchases across devices
- Sign up for a monthly subscription on device A.
- Install the app on a device B before the subscription expires.
- On device B, log in to the same sandbox account that was used on device A.
- Launch the app on device B.
- Tap the Restore Purchases button.
Cancellations
- Sign up for a monthly subscription. Check to see that your app is in the subscribed state.
- Go to the Settings app > App Store > your sandbox account > Manage > Subscriptions and select your existing subscription.
- Cancel the subscription and agree to the terms.
- Close the app and wait 10 minutes.
- Launch the app.
- No active subscriptions should be found.
Upgrades, downgrades, and crossgrades
The sandbox subscription management screen in the Settings app is available as of iOS 14. If you’re testing older iOS versions, you’ll need to expose buttons or another means within the app to test purchases that trigger upgrades, downgrades, and crossgrades.
- Sign up for a monthly subscription.
- Close the app.
- Go to the Settings app > App Store > your sandbox account > Manage > Subscriptions and select your existing subscription.
- Change your subscription by selecting a different product from the list of options..
- If you downgraded your subscription, wait 5-10 minutes.
- Launch your app.
- The user should be subscribed to the new product.
Interrupted purchases
NOTE: This behavior is only possible as of iOS 14.
- Go to App Store Connect > Users and Access > Sandbox Testers, and enable “Interrupt Purchases for This Tester.”
- Purchase an auto-renewable subscription while you’re logged in as this user.
- When the terms and conditions dialog appears, decline the terms and conditions by tapping “Cancel”.
- Check to see if your app unlocked the purchase (it shouldn’t). The receipt should not contain the purchase that you just tried to make.
- Try purchasing again, but this time, accept the terms and conditions.
- Refresh your app, and you should have a valid subscription purchase.
Important takeaways
- Subscriptions renew at an accelerated rate in the sandbox.
- There’s no receipt available in the sandbox until a purchase is made.
- If you’re testing with StoreKitTest (see the section below), don’t forget to periodically test your app in the actual sandbox environment.
Trial eligibility in the developer sandbox
If your app contains products with introductory offers to give users a free trial, you will need to test that only users who are eligible for those offers can see the trial information on your paywall. Using a fresh sandbox tester account, you can run your app and ensure the correct copy displays for products with introductory offers. Then you can purchase the product, immediately cancel, and wait for trial to expire based on the shortened sandbox durations specified above.
After the trial has expired, you can now open your paywall and view your product as an account that should no longer be eligible to receive the trial. Your copy should reflect this by no longer stating the user will receive a free trial. To run through the test again, you can reset your sandbox user’s trial eligibility by following these instructions from Apple.
Further reading
- Test In-App Purchases (Apple docs)
- Testing In-App Purchase Transactions (Apple docs)
StoreKitTest
As of iOS 14, you can test StoreKit locally in Xcode using the StoreKitTest framework. It allows you to simulate Apple’s entire StoreKit system, including adding products in App Store Connect, purchasing products, and going through the subscription lifecycle. It’s often faster than testing in Apple’s sandbox environment. We recommend using StoreKitTest to test some aspects of your app like locking and unlocking content. However, you should always do at least some final testing in sandbox to make sure your configuration in App Store Connect is working correctly.
Setting subscription renewal rates in StoreKitTest
By default, subscriptions made using StoreKitTest will renew in real-time. You can change the renewal rate of your StoreKitTest Configuration File by opening the file in Xcode and going to Editor > Subscription Renewal Rate and choosing one of the renewal rates.
iOS testing with TestFlight
TestFlight behaves like the sandbox but uses production App Store accounts. An app distributed via TestFlight will automatically use the production sandbox environment for purchases. Users cannot be charged in a TestFlight build, but they can view the paywall and step through the purchase process without actually spending money.
Subscription renewal rates in the production sandbox
Subscription lengths are significantly shortened for testing purposes in TestFlight. This allows developers to quickly and repeatedly test purchases, renewals, and expirations. (See the chart above for details.)
Switching between purchase states in TestFlight
To make testing easier, it can be helpful to add a button or secret gesture to the TestFlight build of your app that switches the app between various purchase states. (Make sure to set a build flag that removes this in the App Store build!) And don’t forget to mention this in the TestFlight release notes when you recruit beta testers to help test the paywall.
Keep in mind that while TestFlight uses the production sandbox environment, the build that is submitted to TestFlight should be the same one that gets published to the App Store. If you have a backend with a staging environment and a production environment, this means that TestFlight runs on your production backend, but the purchases go to the production sandbox environment. A typical setup is:
App Version | App Store | Your Backend |
Sandbox | Sandbox | Staging |
TestFlight | Production Sandbox | Production |
App Store | Production | Production |
Communicating with TestFlight beta testers
Because of the limitations of testing subscriptions with TestFlight (especially the short duration of subscriptions in the production sandbox), many developers choose to automatically unlock all features for beta testers.
One thing you definitely don’t want to do is rely on beta testers in TestFlight to test the price of your app, A/B test paywalls, or anything else of the sort. Apple doesn’t allow developers to actually charge TestFlight users, and most beta testers know that, so your results would be skewed. And even if Apple did allow developers to charge TestFlight users, beta testers are typically some of your most loyal users— not at all a representative sample of people coming to the app for the first time.
That said, it can be helpful to have a few beta testers at least view the paywall and/or walk through the steps from triggering the paywall all the way to making a sandbox purchase. The tricky part is explaining this process to testers in a way that will result in helpful feedback.
Let’s be honest… most people don’t read the fine print. At best, they skim. So locking everyone out of your app’s premium features and mentioning the limitations of the beta in the release notes isn’t a great idea. You’ll end up with confused beta testers who don’t get to test all the premium features.
One option is to unlock all features for beta testers, then mention paywall testing instructions in the release notes. That way, the people who actually read the notes can help with paywall testing and everyone else can focus on testing the premium features.
It can be helpful to add a button or secret gesture to the TestFlight build that will revert the app to the free state and allow savvy beta testers to turn this on and encounter the paywall as normal users would once the app is live on the App Store.
Testing procedures
Your testing procedures for TestFlight should be the same as the ones you follow in the sandbox.
Further reading
- Testing Apps with TestFlight (Apple docs)
- Test In-App Purchases (Apple docs)
- Beta Testing Made Simple with TestFlight (Apple docs)
iOS testing in production
For an app that has yet to be released on the App Store, getting an early version of the app approved is a great way to test subscriptions. There are a few tricks to testing in production even before a release hits the App Store, but you’ll also want to keep testing live on the App Store as your app is updated.
Pre-launch testing
- Submit a beta version of the app to App Review. Make sure to set “Version Release” to “Manually release this version” so that the app is not released on the App Store.
- Generate promo codes for the app. This can be done for free apps that are approved but not yet live on the App Store.
- Download the app from the App Store using a promo code.
- Subscribe.
Since this app has gone through approval, subscriptions will perform exactly as they will when the app is live on the App Store—including charging testers who subscribe and allowing testers to manage their subscription on the App Store app. You can give testers promo codes to let them test the app for free. Subscriptions paid for via promo code work exactly like paid subscriptions except that they don’t auto-renew.
One other thing to note here is that apps downloaded via promo codes before the app is live on the App Store don’t seem to have a proper receipt file in the download bundle. The receipt should be refreshed with a purchase, but this issue could be used to test the rare scenario where real users of the app somehow end up in a state where an accurate receipt is not contained in the app bundle.
Adding new products
Renewals and expirations
One thing to watch out for is that the app and in-app purchases (products) might not propagate to the App Store at the same time. This seems to only impact new products, not products that have previously been released with the app. It can take more than 24 hours for a new app, app update, or new product to be available on the App Store, and the app/update might show up hours before the products can be purchased. This means that you’ll be able to download the production version of the app, but the app won’t be able to buy any products. Be patient and try again 24+ hours after the app and products are approved.
Testing procedures
Testing renewals and expirations is difficult in production, since the subscriptions aren’t shortened. To test a monthly renewal in production, you have to wait a month. To test the expiration of an annual subscription, you have to wait a year. This is impractical, which is why Apple shortens subscription durations in the sandbox testing environments. If renewals and expirations are working correctly in those environments, they should work just fine on the App Store.
Cancelations
- Subscribe to a monthly subscription.
- Make sure the app enters the subscribed state with features unlocked.
- Go to the App Store app and cancel the subscription.
- Wait a minute or two to give Apple time to add the cancelation to the payment queue.
- Reopen the app.
At this point, auto-renew should be disabled on the receipt. The app should remain in the subscribed state, then revert to the unsubscribed state after the current billing period ends.
Refunds
- Subscribe to a monthly subscription.
- Make sure the app enters the subscribed state with features unlocked.
- Contact Apple and request a refund.
- Wait a day to give Apple time to process the refund and add it to the payment queue.
- Reopen the app.
At this point, the refund event should be read from the payment queue and the app should revert to the unsubscribed state.
Restoring purchases
Ideally, your app will automatically determine the subscription state and unlock or lock the app. But even if that’s working as expected, receipts sometimes need to be refreshed.
- Subscribe to the app.
- Delete the app, restart the device, and reinstall the app.
- Launch the app.
- If necessary, tap the “Restore Purchases” button.
An active subscription should be found and the app should change to the subscribed state.
Free trials and introductory offers
Since free trials are a type of introductory offer, most subscription app developers use this feature even if they don’t realize it.
The most important thing here is to make sure your app only presents introductory offers to users who are eligible for them.
One thing to note is that once an account has redeemed an introductory offer, that account is no longer eligible for introductory offers for any product within the same subscription group. So if you test a free trial, that offer will no longer be available to that account (there doesn’t seem to be a way to reset this other than to create a fresh sandbox account).
Ideally your app should check introductory offer eligibility and hide “Free Trial” and other offer-related messaging when the user is not eligible. However, getting this wrong is a frequent cause for rejection, so err on the side of making everyone eligible instead of everyone ineligible.
Further reading
- Testing Introductory Offers (Apple docs)
- Implementing Introductory Offers in Your App (Apple docs)
Testing Subscription Offers
iOS Subscription Offers can only be redeemed in sandbox after a subscription has expired. This is related to the fact that product changes don’t work in sandbox. In production, an active or lapsed subscriber will be able to redeem an iOS Subscription Offer.
Further reading
- Implementing Subscription Offers in Your App (Apple docs)
- Setting Up Subscription Offers (Apple docs)
- Subscription Offers Best Practices (WWDC 2019)
- Signing iOS Subscription Offers with RevenueCat (RevenueCat blog)
QA checklist
Use this handy checklist to make sure you’ve covered your bases testing your in-app subscriptions.
In the developer sandbox:
- Test purchasing a subscription
- Test subscription renewals and expiration
- Test restoring a purchase after a subscription expires
- Test restoring purchases during an active subscription
- Test restoring purchases across devices
In TestFlight:
- Test purchasing a subscription
- Test subscription renewals and expiration
- Test restoring a purchase after a subscription expires
- Test restoring a purchase during an active subscription
- Test restoring purchases across devices
In production:
- Test making a purchase before launch day
- Test making a purchase on launch day
- Test cancelation a subscription
- Test refunding a purchase
- Test restoring a purchase
Bonus iOS testing tips
We’ve learned a lot about implementing and testing in-app subscriptions in iOS over the years. Here are a few of the tips and tricks we’ve picked up along the way.
Using promo codes for subscriptions
You can generate promo codes for subscriptions in App Store Connect, but there are several caveats to be aware of before you decide to use them.
- A subscription granted via promo code will not auto-renew. So if you give someone a promo code for a free month, they get that month free, then the subscription ends and they have to purchase the subscription using the normal in-app flow.
- If you give a promo code for a free month to an existing subscriber, using it will cancel their existing subscription and give them a free month that won’t auto-renew.
- If your app is a paid app, you’ll have to generate a separate promo code for downloading the app first.
- The receipt contains no indication that a subscription was purchased using a promo code, so you can’t easily track this in your metrics.
Using offer codes for subscriptions
Offer codes work a lot like promo codes for subscriptions but without the many caveats that apply to promo codes. Additionally, offer codes can be redeemed either within a payment screen in your app or in the App Store (via a deep link). The only caveat is that offer codes can’t be fully tested in the sandbox environment. As long as you can test that purchasing subscriptions works in sandbox, you can trust that offer codes will work in production.
TestFlight invites
You don’t need to collect a user’s App Store account email address in order to invite them to TestFlight. You can send an invite to any email address. When a user taps the unique link they receive in a TestFlight email, it will associate the invite with whatever App Store account is currently logged in to that device. Future beta emails still go to the original email address the invite was sent to, and Apple does not add the App Store account email address to your TestFlight tester list.
Missing receipts
When an app is downloaded from the App Store, there should always be a receipt in the app bundle. But sometimes that just doesn’t happen. One theory is that when a user backs up and restores their device using iTunes, the receipt is not transferred with the app (likely to prevent tampering with the receipt).
You’d think iOS would automatically download a fresh receipt in cases like this, but no. This is why it’s important to handle the edge case of an app bundle without a receipt and provide an easy-to-find Restore Purchases button to retrieve that receipt.
Trying to automatically download a fresh receipt in these cases may seem like a good idea, but refreshing a receipt triggers a system-level request for a password, which many users will just cancel. Instead of automatically downloading the receipt, present a Restore Purchases button to the user that they can use as needed.
Adding new products
Once your app is live with subscriptions, you can add new products and get them approved without an app update through App Store Connect. It’s important to remember that after new products are approved, they can take up to 24 hours to propagate throughout the App Store and become available to users. Because of this, you should set up your paywall such that you can enable new product offerings remotely.
Here’s how this process should look:
- Your app is live on the App Store with monthly_product_1.
- You create a new monthly_product_2 in App Store Connect and submit it for approval.
- Apple approves monthly_product_2.
- Wait 24 hours.
- Switch your app to display monthly_product_2 through some remote configuration setting.
Subscription disclosures
Apple used to require apps to disclose all sorts of terms and conditions on the paywall screen, but those requirements have since been relaxed. You don’t even have to link to your Terms of Service and Privacy Policy on the paywall anymore (but they must be linked somewhere inside the app).
The only three things now required on your app’s paywall are the:
- Subscription name
- Subscription length
- Subscription price (and price per unit, if appropriate)
Final thoughts
We hope this guide to iOS testing for subscription apps was helpful to you. Is there something we’ve missed? Feel free to Tweet us or contact us via the website.
If you’re struggling with implementing or scaling your in-app subscriptions, check out RevenueCat’s open-source SDKs. They handle the pain points of in-app subscriptions, so you can get back to building your app.
You might also like
- Blog post
How we built the RevenueCat SDK for Kotlin Multiplatform
Explore the architecture and key decisions behind building the RevenueCat Kotlin Multiplatform SDK, designed to streamline in-app purchases across platforms.
- Blog post
Inside RevenueCat’s engineering strategy: Scaling beyond 32,000+ apps
The strategies and principles that guide our global team to build reliable, developer-loved software
- Blog post
RevenueCat Ship-a-ton
The hackathon that’s all about shipping… a ton.