TECH

CASE STUDY

End-to-end Testing In-App Purchases in React Native Apps: Everything You Need to Know

Published: August 22, 2024

22 min read

Writing end-to-end tests in React Native can be a challenging task, especially when it comes to testing in-app purchases. In order to test in-app purchases, it's necessary to simulate a purchase flow from start to finish. It can be difficult to do with traditional testing methods.

Additionally, various factors can impact a purchase's success, such as network connectivity, payment processing, and authentication.

If you use a ready payment processing solution like Stripe, e2e testing can be done using its test mode. Developers can test the entire purchase flow without incurring any actual charges or processing any real payments.

Sometimes you need to add in-app purchases such as subscriptions that are processed by App Store or Google Play. That invokes working with the native operating system components that are out of React Native app scope. Getting access and interacting with them is quite challenging due to the technical restrictions there’re now.

During our work on this challenge, we gained some insights into how to make end-to-end testing of in-app purchases possible in React Native using real devices as well as simulators. With this, we can be sure users can make purchases and the app is secure and reliable by testing the purchase flow.

Tech stack we will work with: React Native, Detox, Appium, React-Native-iap.

 
 

✍️ Detox and Appium: The Options You Have

Detox and Appium are both popular testing frameworks that can be used to automate end-to-end tests for mobile applications. Of course, including React Native apps.

Detox is a powerful testing framework that’s specifically designed for React Native apps. It provides a comprehensive set of APIs for writing end-to-end tests that can simulate user interactions and verify app behavior across multiple devices and platforms.

Pros

  1. Fast and reliable test automation
  2. Support for both iOS and Android platforms
  3. Asynchronous approach, preventing flakiness in the app
  4. Integration with various testing frameworks and tools, such as Jest and Mocha

Cons

  1. Limited support for non-React Native apps
  2. Not supported for real iOS devices
  3. Limited options to interact with system elements, like dialogs, web views, etc.
  4. Difficult integration to the app

Appium is another popular open-source testing framework that supports automated testing for various mobile platforms, including iOS and Android. It uses the WebDriver protocol to automate interactions with mobile apps and supports a wide range of programming languages, including JavaScript.

Pros

  1. Support for a wide range of mobile platforms and programming languages
  2. Integration with various testing frameworks and tools, such as Selenium
  3. Can test both native and hybrid mobile apps
  4. Supports real device and emulator testing

Cons

  1. Slower test execution
  2. Limited support for React Native-specific features
  3. Difficult to set up
  4. Limited support for gestures

Based on our experience, if you have minimum system-related features in the app, (such as alerts, dialogs, web views, sign-in with social provides), Detox is the best option to use for end-to-end tests for your React Native app. Though the integration into the existing app can be pretty challenging at first, the process of writing tests is pretty smooth and straightforward.

Unfortunately, at the moment of writing this article, Detox version is not supported for real iOS devices, as well as interacting with elements that are out of your React Native app. That makes the process of testing in-app purchases using Detox impossible on iOS. Besides, you can’t log in to the app via web providers, like Google or Apple, on iOS as well.

Though, with some workarounds, it’s possible on Android. Thus, the entire e2e testing flow can’t be achieved only by Detox. That’s why we also consider Appium for this purpose, which gives us an opportunity to interact with the functionality outside the React Native scope on iOS.

By the way, React Native fitness apps often include in-app purchase features. Hence, our developers test both the iOS and Android versions of the app to ensure the reliability of in-app purchases.

In the next sections, we’re going to show you how to test in-app subscriptions on Android using Detox and on iOS using Appium. Why do we need different frameworks for every platform? As we mentioned above, Detox can’t do it on iOS. Appium can also be used on Android, but we can’t write tests for React Native Android apps straightforwardly, unfortunately.

The reason is the complexity of locating and interacting with elements by the id property. Here you can find a discussion on this topic and how you can work around it. If it works fine for you, feel free to use Appium for both platforms. But we also hope that in the near future, Detox will allow us to execute such tests for both platforms.

Improve the reliability and functionality of in-app purchases in your React Native app with end-to-end testing. Our guide covers the key concepts and best practices you need to know!

Contact Us

 
 

⚙️ Preparing the App

We will use a very basic app to demonstrate how to test the buying in-app purchases. On the first tab, we will list all items (products and subscriptions). On the second one, we will show the list of available items, i.e. the ones the user has purchased and are currently available. We don’t implement anything specific, but you can follow our repository as a reference.

To fetch and interact with the purchases in the app, we‘re going to use the react-native-iap library. react-native-iap is a library for React Native that provides an easy-to-use interface for in-app purchases (IAP) on both iOS and Android platforms. It allows developers to implement IAP functionality without having to write custom native code for each platform.

In this implementation, we will skip verifying receipts for in-app purchases, since it should be done on the server. But make sure you are doing it in your production app.

*Verifying receipts for in-app purchases involves checking the validity and authenticity of the purchase receipt that is generated when a user makes an in-app purchase.

This is done by sending the receipt to the store's server, which then verifies the receipt and sends a response indicating whether the receipt is valid or not.

It’s a necessary step to prevent fraud, comply with app store policies, grant access to content, and track user purchases and behavior.*

To enable in-app purchases for your React Native app, you will need to create products and/or subscriptions in the app stores. For Android, you can follow this documentation to create in-app products and this one for subscriptions. For iOS, you can follow this link for information on creating in-app products and this link for information on creating subscriptions.

Android emulator can be with the pre-installed Google Play Store, which means that we can run our tests on the emulator right as on the real device. Just make sure you added your testers as license testers.

IOS simulators are more limited in their functionality. But we still can test purchases on simulators using a sandbox environment of StoreKit. The sandbox environment simulates the App Store's purchase process and allows developers to test their in-app purchases without incurring real charges.

IOS simulators are also important for ensuring that the purchase process works correctly. Plus, it’s needed to test the various scenarios during a purchase. So, if you need to run your tests on CI/CD, this approach can be helpful.

 
 

🌱 Testing In-App Purchases Using Detox on Android

Firstly, prepare your workspace and install all required libraries and tools following the official Detox documentation. Make sure you have a compatible React Native version, and in case of any trouble go through their troubleshooting guide or issues.

Android Emulator 💾

Detox integration into the Android part can be quite challenging. The Detox team did great work providing detailed information on how to create a stable testing process on Android. The problem we have with this setup is that AOSP emulators as they’re designed don’t support Google Services. In-app purchases depend on Google Services, especially Google Play Store.

It also means that we can’t use Test Butler for testing. In case your project doesn’t depend on Google Services, we encourage you to use AOSP emulators with Test Butler configured to have a stable, reliable environment for running automated UI tests.

But our workflow requires Android emulators with Google Play Store. You can create one using this documentation. Make sure you select a hardware profile that includes Play Store. Once you do it, launch it and go to the Play Store to log in to the license testing account you specified in the Play Console for testing.

Detox Setup 🦉

When you finish integrating Detox into the project, try to run a simple test with some actions, like click or swipe. If it works as expected, you’re good to go to write tests. Be prepared to spend some time fixing issues since the React Native team is actively introducing new features and functionality such as New Architecture that can produce new challenges.

We’re developing our apps using TypeScript. And since version 0.71 when you create a new React Native app via the React Native CLI you'll get a TypeScript app by default. If you want to write Detox tests using TS as well, follow these steps:

  1. Add to your detox config file the next section:

When exposeGlobals variables are set to true it forces Detox to expose device, expect, element, by and waitFor as global variables. When false, you should import them explicitly instead:

You can read about the behavior section here.

  1. Add ts-jest as dev dependency:
  1. Update e2e/jest.config.js file:
  1. Now you can change the extension of your detox file from .js to .ts.

Let’s write tests to validate in-app purchases functionality.

Writing Tests 💡

Writing tests with Detox involves creating a test suite that interacts with your React Native app and verifies that it behaves as expected.

Tests are typically structured using describe and it blocks to define the scope of each test case, and beforeEach and afterEach blocks to set up and tear down the app state for each test.

Inside each test case, you'll use Detox's API to simulate user interactions with the app, such as tapping buttons, scrolling through lists, or entering text into input fields. You'll then use assertions to check that the app has responded as expected, such as by checking that a specific element is visible or that the app has navigated to the correct screen.

Detox tests work by using a combination of Jest and the Detox library to run the app in a simulated environment and interact with it programmatically. The Detox library provides a set of APIs that allow you to interact with the app in a way that mimics user behavior, while Jest provides a framework for organizing and running your tests.

When you run your test suite, Detox will launch your app in a simulated environment, perform the actions specified in your tests, and verify that the app behaves as expected.

We have a list of products and subscriptions on the main screen. Following Detox guide, we add testId to each item in the list, as well as their children, so we can access and assert different items.

For example:

// Purchases.tsx

// ProductItem.tsx

Let’s write the first tests to ensure we have lists of products and subscriptions.

// e2e/tests/main.test.ts

We will run our tests in debug mode now, so we need to have React Native packager running in parallel before we start Detox tests.

We also should build the app. It shouldn’t be done every time you want to run tests, just in case of some major changes like adding new libraries, etc.

We select configuration android.emu.debug since we want to run the android app on the emulator in debug mode. We chose this config from the .detoxrc.js config file.

Feel free to update configurations, adding or removing ones, to make them most appropriate for your app structure (it’s especially helpful if you have different flavors and schemas)

Now we can run our first test:

You can save these commands in package.json as shortcuts:

We have a successful result — it means we can go further and try to buy a product.

detox test

To buy a product, we use requestPurchase method from react-native-iap library (we’re using useIAP hook). This is the implementation according to the current library implementation.

In case of any errors, we show the respectful error modal. And we check the current purchase in useEffect:

Feel free to re-write these functions to make them work for you. We use successful and error modals to validate the result. We pass success and error as testIds params respectfully.

Let’s write the test to locate the buy button of the first product and tap on it:

// e2e/tests/main.test.ts

This is what we see by tapping:

review and agree

Since working with IAP requires interacting with AppStore or Play Store dialogs, usually it forces developers to mock it for e2e tests. In Detox, mocking refers to the process of simulating external dependencies or behaviors of the application that are not under test.

It allows isolating the tests from external factors that may affect their reliability or performance, such as network latency or third-party services. But sometimes it’s vital to be able to check even such processes automatically due to high-valued features. That’s why we will try to work around this issue.

As we see in the screenshot, this is a system modal that is out of React Native's scope. Unfortunately, at the moment of writing this article, interactions with systems widgets, buttons and so are limited. Though there’s an ability to find elements by type, sometimes it doesn’t work.

Using UI Automator Viewer we got the android class of the button — android.widget.Button. But Detox can’t find this element — the app seems to it to be idle. If you don’t face this issue, please, use this way to access systems elements further.

To overcome this issue, we’re going to use another method — working with UiAutomator’s UiDevice API. Be cautious: this Detox method is not stable, as well as the way we’re going to use it — clicking on coordinates.

We can’t guarantee that the coordinates of some system elements will be the same in the future and that they are identical for different devices. But the advantage that it gives us is crucial. We can access elements that don’t belong to our app, such as those managed by BLE Manager React Native. It also means that it can be used for things like social login.

In the emulator’s Developer options, we enabled “Pointer location” as it shows touch data. We need to find out what approximate coordinates the center of the button has. We’re going to use these coordinates to click specifically on this system button.

As you see in the screenshot, the approximate coordinates are x — 550, y — 2060.

Review and agree pressed

Let’s extend the test by clicking on the button:

If you’re using Typescript, you probably see the issue: Property 'click' does not exist on type 'void'. The reason is in the incorrectly typed getUiDevice function. If you go to Detox source code, you’ll see the next type: getUiDevice(): Promise<void>. But according to the source code, we can update the type in a next way:

You can extend UIDevice type with other methods from the source code.

It’s a bad practice to use any wait functions (setTimeout) in the tests. It can introduce timing issues that can make the tests unpredictable and difficult to debug. If you use setTimeout to delay the test for a certain amount of time before checking the visibility of the element, you may find that the test sometimes fails even though the element is actually visible.

This is because the timing of the test is not consistent and may be affected by factors such as the performance of the device or network connectivity. To avoid these timing issues, Detox provides its own waitFor function that allows you to wait for specific conditions to be met before proceeding with the test.

But the thing is when we use getUiDevice method and work with the returned instance, we can’t find elements by their properties. So we can’t wait for the element, because we don’t have it. That’s why we found out the coordinates of the button.

This is the reason why one of the key features of Detox — the ability to automatically synchronize the test execution with your app — isn’t working when the system dialog is shown. We need to add a wait function before getting uiDevice and after clicking to give the system time to process actions.

This is the screen you should see next. The button is located within the same coordinates, so we can add a new click function.

one tap buy

If the purchase is successful, we should see the respectful modal:

successul purchase

To confirm it, we will extend the test with this assertion:

In case, there will be an error or the app sticks, this test fails as expected.

The first in-app purchase test is ready. Congratulations! Let’s make the same for subscriptions.

For subscriptions, there is only one action button — Subscribe. It has the same coordinates as the product buttons.

test subscription

So, the test for subscriptions is the next one:

And this is the final result we have:

pass

Congratulations on writing tests to check in-app purchases for Android using Detox. Now you can update them for your app. Meanwhile, we’re going to write tests for iOS using Appium.

 
 

👩‍💻 Testing In-App Purchases Using Appium on iOS

Appium Setup 🎯

First, let’s take a look at how Appium executes tests:

  1. Appium Server

    It’s started on a local or remote machine. This server acts as a bridge between the test scripts and the mobile device/emulator.

  2. Test Scripts

    Appium provides client libraries for various programming languages such as Java, Python, Ruby, etc. The test scripts are written using these client libraries and the testing framework of your choice (e.g., JUnit, TestNG, etc.).

  3. Desired Capabilities

    The test scripts send a request to the Appium server with desired capabilities, which includes information about the device, platform, app package, app activity, etc. The server uses this information to launch the app on the device or emulator.

  4. Session Creation

    Once the desired capabilities are sent, a new session is created between the Appium server and the device/emulator.

  5. WebDriver Protocol

    Appium uses the WebDriver protocol to interact with the app. The test scripts use the WebDriver API to perform actions on the app, such as tapping a button, swiping, entering text, etc.

  6. Appium Drivers

    Appium provides different drivers to interact with different types of apps. For example, there are different drivers for Android, iOS, and Hybrid apps.

  7. Element Identification:

    To interact with the app, the test scripts need to identify the elements of the app, such as buttons, text fields, etc. Appium uses different methods to identify elements, such as XPath, ID, class name, accessibility ID, etc.

  8. Test Execution

    The test scripts execute the test steps using the Appium drivers and the WebDriver API. Appium records the test results and sends them back to the test scripts.

  9. Test Reporting

    The test scripts can use various reporting tools to generate reports on the test results.

To read more details about Appium philosophy, design, and concepts, follow the official documentation. As well as additional information regarding Appium CLI, supported derives and so on.

We need to have Appium installed globally to access it from the command line simply by running the appium command:

After successful installing, run the next command to make sure it works correctly:

To write and execute tests, let’s create a new project to install all Appium dependencies separately from the app (you can do it in a sibling folder to your app one to have everything in a monorepo):

Now, we can install webdriverio as a dev dependency:

To execute tests using a WebdriverIO testrunner command line interface, we need to install @wdio/cli:

To set up and configure our test environment, including the test framework, the test runner, and the various tools and services we'll use during testing, we need to use a configuration file in WebDriverIO. It’s a JavaScript file that defines the settings and options for your WebDriverIO test suite. It typically has the filename wdio.conf.js and is located in the root directory of your project.

In this configuration file, you can set various options such as the test framework you want to use, the test runner, the browser or browsers you want to run your tests in, the test spec files to include, and many more.

To create this file, we can use WDIO configuration wizard:

These are answers you can select in the survey:

e2e

You can adjust them in the config file (wdio.conf.ts) in case the structure of your project has changed. Check out the official documentation for more in-depth instructions.

This is the final version of our config file:

In the setup.ts file, we have platforms capabilities (here you can see the example for Android as well):

To execute tests on iOS, we need to install XCUITest Driver. Here is how you can do it:

To execute tests, you can add a script to package.json:

Now, we’re ready to write tests.

Appium Inspector 🦾

In writing tests, it’s vital to understand how we can access UI elements, especially, if we need to access system ones. Since React Native app can be considered a hybrid one, it’s good to have access to the tool that allows you to inspect and interact with the elements of a mobile application while it's running on an emulator, simulator, or real device. This can be done by Appium Inspector.

Using Appium Inspector, you can inspect the UI elements of your application and view their attributes, such as IDs, classes, accessibility labels, and more. This information can be used to write Appium tests that interact with those elements, such as clicking buttons, entering text, or navigating through the app.

In addition to inspecting elements, Appium Inspector also provides a console for executing Appium commands and a recorder for recording user interactions as Appium code. This can be a useful tool for creating Appium test scripts, especially if you're new to Appium or mobile automation.

You can use the web application to interact with your app. Make sure you run the Appium server with allow-cors mode (appium --alow-cors). Copy the capabilities from your WDIO config file to access the app via Inspector.

This is how it looks like with the first button selected:

products

Writing Tests 🎲

Finally, we can write tests. We will use a built-in assertion library from the WDIO. This library extends the Matchers functionality of Jest by providing additional matchers optimized for end-to-end testing.

Here is a first test to check if the products list has items:

Now, if the test is successful, we can try to buy a product. Using Appium Inspector, we can find out how to locate a native “Purchase” button in the system dialog:

xcode

We can locate it using XPath:

If this step is successful, you can see the next dialog. We need to tap on OK button:

you're all set

And after tapping on OK, you should see our custom successful modal. This is the full code of this step:

Congratulations, you finished iOS part. You can extend it with tests for subscriptions using this principle.

💎 Conclusion

In conclusion, testing React Native apps for Android with Detox and React Native for iOS using Appium is an effective way to ensure the quality and reliability of in-app purchases. Detox provides a robust testing framework that allows for seamless end-to-end testing, while Appium offers cross-platform support for iOS and Android testing.

Overall, testing React Native apps with Detox and Appium is a valuable investment for app developers looking to deliver high-quality apps that meet the needs of their users. With thorough testing and quality assurance, developers can build trust with their users and establish a reputation for reliability and excellence in the highly competitive mobile app market.

Ready to streamline your in-app purchase testing process? Contact us!

Read also

How can we help you?

Our clients say

Stormotion client Alexander Wolff, CPO from [object Object]

When I was working with Stormotion, I forgot they were an external agency. They put such effort into my product it might as well have been their own. I’ve never worked with such a client-focused company before.

Alexander Wolff, CPO

Sjut