---
title: Automated App testing on iOS and Android with Java and JUnit
description: Run automated tests against your iOS and Android apps with Java and JUnit
  on the TestingBot Real Device Cloud.
source_url:
  html: https://testingbot.com/support/app-automate/appium/java
  md: https://testingbot.com/support/app-automate/appium/java/index.md
---
### Java Examples:

- [TestNG](https://testingbot.com/support/app-automate/appium/java/testng)

# JUnit Automated App Testing
  

See our [JUnit example repository](https://github.com/testingbot/java-junit-app-example) for a simple example on how to run JUnit mobile app tests.

## Installation

First, make sure you install the necessary dependencies to run a test. For this example, we'll be using [JUnit 5](https://junit.org/junit5/) and the [Appium Java Client](https://github.com/appium/java-client).

Add the following dependencies to your `pom.xml`:

    <dependencies>
        <!-- JUnit 5 -->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>5.10.2</version>
            <scope>test</scope>
        </dependency>
        <!-- Appium Java Client -->
        <dependency>
            <groupId>io.appium</groupId>
            <artifactId>java-client</artifactId>
            <version>10.0.0</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

**Note:** Appium Java Client 10.x requires Java 11 or higher.

## JUnit Example

The example below shows a complete JUnit 5 test that connects to the TestingBot hub and runs a test on a real device. In this test, we'll test a sample calculator app by entering values into two input fields and verifying the sum.

[Android](https://testingbot.com#)[iOS](https://testingbot.com#)

    import io.appium.java_client.AppiumBy;
    import io.appium.java_client.android.AndroidDriver;
    import io.appium.java_client.android.options.UiAutomator2Options;
    import org.junit.jupiter.api.*;
    import org.openqa.selenium.WebElement;
    import org.openqa.selenium.support.ui.ExpectedConditions;
    import org.openqa.selenium.support.ui.WebDriverWait;
    
    import java.net.MalformedURLException;
    import java.net.URL;
    import java.time.Duration;
    
    import static org.junit.jupiter.api.Assertions.assertEquals;
    
    public class AndroidAppTest {
        private AndroidDriver driver;
    
        @BeforeEach
        public void setUp() throws MalformedURLException {
            // Configure capabilities using UiAutomator2Options
            UiAutomator2Options options = new UiAutomator2Options()
                .setPlatformName("Android")
                .setPlatformVersion("16.0")
                .setDeviceName("Pixel 9")
                .setApp("tb://<your-app-id>")
                .setAutomationName("UiAutomator2");
    
            // Add TestingBot-specific options
            options.setCapability("tb:options", java.util.Map.of(
                "key", "YOUR_TB_KEY",
                "secret", "YOUR_TB_SECRET",
                "name", "Android Calculator Test",
                "build", "JUnit Build 1"
            ));
    
            // Connect to TestingBot hub
            driver = new AndroidDriver(
                new URL("https://hub.testingbot.com/wd/hub"),
                options
            );
        }
    
        @Test
        public void testCalculatorSum() {
            WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(30));
    
            // Enter first number
            WebElement inputA = wait.until(
                ExpectedConditions.elementToBeClickable(AppiumBy.accessibilityId("inputA"))
            );
            inputA.sendKeys("10");
    
            // Enter second number
            WebElement inputB = wait.until(
                ExpectedConditions.elementToBeClickable(AppiumBy.accessibilityId("inputB"))
            );
            inputB.sendKeys("5");
    
            // Verify the sum
            WebElement sum = wait.until(
                ExpectedConditions.elementToBeClickable(AppiumBy.accessibilityId("sum"))
            );
            assertEquals("15", sum.getText());
        }
    
        @AfterEach
        public void tearDown() {
            if (driver != null) {
                driver.quit();
            }
        }
    }

    import io.appium.java_client.AppiumBy;
    import io.appium.java_client.ios.IOSDriver;
    import io.appium.java_client.ios.options.XCUITestOptions;
    import org.junit.jupiter.api.*;
    import org.openqa.selenium.WebElement;
    import org.openqa.selenium.support.ui.ExpectedConditions;
    import org.openqa.selenium.support.ui.WebDriverWait;
    
    import java.net.MalformedURLException;
    import java.net.URL;
    import java.time.Duration;
    
    import static org.junit.jupiter.api.Assertions.assertEquals;
    
    public class IOSAppTest {
        private IOSDriver driver;
    
        @BeforeEach
        public void setUp() throws MalformedURLException {
            // Configure capabilities using XCUITestOptions
            XCUITestOptions options = new XCUITestOptions()
                .setPlatformName("iOS")
                .setPlatformVersion("26.0")
                .setDeviceName("iPhone 17")
                .setApp("tb://<your-app-id>")
                .setAutomationName("XCUITest");
    
            // Add TestingBot-specific options
            options.setCapability("tb:options", java.util.Map.of(
                "key", "YOUR_TB_KEY",
                "secret", "YOUR_TB_SECRET",
                "name", "iOS Calculator Test",
                "build", "JUnit Build 1"
            ));
    
            // Connect to TestingBot hub
            driver = new IOSDriver(
                new URL("https://hub.testingbot.com/wd/hub"),
                options
            );
        }
    
        @Test
        public void testCalculatorSum() {
            WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(30));
    
            // Enter first number
            WebElement inputA = wait.until(
                ExpectedConditions.elementToBeClickable(AppiumBy.accessibilityId("inputA"))
            );
            inputA.sendKeys("10");
    
            // Enter second number
            WebElement inputB = wait.until(
                ExpectedConditions.elementToBeClickable(AppiumBy.accessibilityId("inputB"))
            );
            inputB.sendKeys("5");
    
            // Verify the sum
            WebElement sum = wait.until(
                ExpectedConditions.elementToBeClickable(AppiumBy.accessibilityId("sum"))
            );
            assertEquals("15", sum.getText());
        }
    
        @AfterEach
        public void tearDown() {
            if (driver != null) {
                driver.quit();
            }
        }
    }

Always call `driver.quit()` in your teardown method to properly end the session. Otherwise, the test will continue running until it times out.

## Uploading Your App

Please see our [Upload Mobile App](https://testingbot.com/support/app-automate/help/upload) documentation to find out how to upload your app to TestingBot for testing.

## Specify Browsers & Devices

To run your tests on TestingBot, you need to configure the device and platform you want to test on. The Appium Java client provides type-safe Options classes for this purpose.

### Using Options Classes (Recommended)

For Android testing, use `UiAutomator2Options`. For iOS testing, use `XCUITestOptions`:

[Android](https://testingbot.com#)[iOS](https://testingbot.com#)

    UiAutomator2Options options = new UiAutomator2Options()
        .setPlatformName("Android")
        .setPlatformVersion("16.0")
        .setDeviceName("Pixel 9")
        .setApp("tb://<your-app-id>")
        .setAutomationName("UiAutomator2");
    
    // Add TestingBot credentials
    options.setCapability("tb:options", Map.of(
        "key", "YOUR_TB_KEY",
        "secret", "YOUR_TB_SECRET",
        "name", "My Test",
        "build", "Build 1"
    ));
    
    AndroidDriver driver = new AndroidDriver(
        new URL("https://hub.testingbot.com/wd/hub"),
        options
    );

    XCUITestOptions options = new XCUITestOptions()
        .setPlatformName("iOS")
        .setPlatformVersion("26.0")
        .setDeviceName("iPhone 17")
        .setApp("tb://<your-app-id>")
        .setAutomationName("XCUITest");
    
    // Add TestingBot credentials
    options.setCapability("tb:options", Map.of(
        "key", "YOUR_TB_KEY",
        "secret", "YOUR_TB_SECRET",
        "name", "My Test",
        "build", "Build 1"
    ));
    
    IOSDriver driver = new IOSDriver(
        new URL("https://hub.testingbot.com/wd/hub"),
        options
    );

### Using BaseOptions (Generic)

For more flexibility or when working with multiple platforms, you can use `BaseOptions`:

    import io.appium.java_client.AppiumDriver;
    import io.appium.java_client.remote.options.BaseOptions;
    import org.openqa.selenium.MutableCapabilities;
    import java.net.URL;
    import java.util.Map;
    
    BaseOptions<?> options = new BaseOptions<>()
        .setPlatformName("Android")
        .amend("appium:automationName", "UiAutomator2")
        .amend("appium:deviceName", "Pixel 9")
        .amend("appium:platformVersion", "16.0")
        .amend("appium:app", "tb://<your-app-id>");
    
    options.setCapability("tb:options", Map.of(
        "key", "YOUR_TB_KEY",
        "secret", "YOUR_TB_SECRET"
    ));
    
    AppiumDriver driver = new AppiumDriver(
        new URL("https://hub.testingbot.com/wd/hub"),
        options
    );

### Required Capabilities

To specify which device you want to test on, you need to provide:

- `platformName` - The mobile OS (Android or iOS)
- `appium:platformVersion` - The OS version (e.g., "17.0", "26.0")
- `appium:deviceName` - The device name (e.g., "Pixel 9", "iPhone 17")
- `appium:app` - The app to test (URL or tb:// reference)
- `appium:automationName` - The automation driver ("UiAutomator2" for Android, "XCUITest" for iOS)

Use the device picker below to generate the configuration for your target device:

  ![OS selected](https://testingbot.com/assets/environments/svg/ios-383468addf160fa18d0e431f529420739d7f7d1206175f682fead627d2e99a52.svg) iOS 18.6 › iPhone 16 

Loading environments...

Please wait while we load the available browsers and platforms.

    

## Testing Internal Websites

We've built [TestingBot Tunnel](https://testingbot.com/support/tunnel), to provide you with a secure way to run tests against your staged/internal webapps.  
Please see our [TestingBot Tunnel documentation](https://testingbot.com/support/tunnel) for more information about this easy to use tunneling solution.

The example below shows how to easily run a JUnit test with our Tunnel:

**1.** [Download our tunnel](https://testingbot.com/support/tunnel) and start the tunnel:

    java -jar testingbot-tunnel.jar key secret

**2.** Update your test to connect through the tunnel instead of directly to the TestingBot hub. Change the URL from `hub.testingbot.com` to `localhost:4445`:

    // Connect through the tunnel instead of directly to TestingBot
    AndroidDriver driver = new AndroidDriver(
        new URL("http://localhost:4445/wd/hub"),
        options
    );

Your test traffic will now go securely through the tunnel, allowing you to test apps that access internal resources.

## Run tests in Parallel

Parallel Testing means running the same test, or multiple tests, simultaneously. This greatly reduces your total testing time.

You can run the same tests on all different browser configurations or run different tests all on the same browser configuration.

TestingBot has a large grid of machines and browsers, which means you can use our service to do efficient parallel testing. It is one of the key features we provide to greatly cut down on your total testing time.

Please see our [Parallel JUnit documentation](https://testingbot.com/support/web-automate/selenium/java/parallel-junit) for parallel testing.

### Queuing

Every plan we provide comes with a limit of parallel tests.   
 If you exceed the number of parallel tests assigned to your account, TestingBot will queue the additional tests (for up to 6 minutes) and run the tests as soon as slots become available.

## Mark tests as passed/failed

TestingBot cannot automatically determine if your test passed or failed - that depends on your test assertions. To report test results to TestingBot, use our [REST API](https://testingbot.com/support/api).

First, add the [TestingBot Java client](https://github.com/testingbot/testingbot-java) to your dependencies:

    <dependency>
        <groupId>com.testingbot</groupId>
        <artifactId>testingbotrest</artifactId>
        <version>1.0.10</version>
    </dependency>

Then use it in your test to report the result:

    import com.testingbot.testingbotrest.TestingbotREST;
    import java.util.Map;
    
    @AfterEach
    public void tearDown() {
        if (driver != null) {
            // Report test result to TestingBot
            TestingbotREST api = new TestingbotREST(
                "YOUR_TB_KEY",
                "YOUR_TB_SECRET"
            );
    
            api.updateTest(
                driver.getSessionId().toString(),
                Map.of(
                    "success", "1", // "1" for passed, "0" for failed
                    "name", "Calculator Test"
                )
            );
    
            driver.quit();
        }
    }

For JUnit 5, you can use a `TestWatcher` extension to automatically report results:

    import org.junit.jupiter.api.extension.*;
    
    public class TestingBotWatcher implements TestWatcher {
        private final AndroidDriver driver;
        private final TestingbotREST api;
    
        public TestingBotWatcher(AndroidDriver driver, String key, String secret) {
            this.driver = driver;
            this.api = new TestingbotREST(key, secret);
        }
    
        @Override
        public void testSuccessful(ExtensionContext context) {
            updateTest("1", context.getDisplayName());
        }
    
        @Override
        public void testFailed(ExtensionContext context, Throwable cause) {
            updateTest("0", context.getDisplayName());
        }
    
        private void updateTest(String success, String name) {
            api.updateTest(driver.getSessionId().toString(), Map.of(
                "success", success,
                "name", name
            ));
        }
    }

## Preparing your App

You do not need to install any code or plugin to run a test.   
 Below are some things that are necessary to successfully run a mobile test:

**For Android:**
- Please supply the URL to your `.apk` or `.aab` file.   
**Important:** the `.apk` file needs to be a x86 build (x86 ABI) for Android emulators.

**For iOS Real Device:**
- Please supply the URL to an `.ipa` file.

**For iOS Simulator:**
- Please supply the URL to a `.zip` file that contains your `.app`
- The `.app` needs to be an iOS Simulator build:   

  - Create a Simulator build:  

    xcodebuild -sdk iphonesimulator -configuration Debug

  - Zip the `.app` bundle:  

    zip -r MyApp.zip MyApp.app

## Additional Options

Appium provides [a lot of options](https://appium.io/docs/en/2.0/guides/caps/) to configure your test.

Some important options that might help:

**For Android:**
- **appActivity** and **appPackage** : by default, Appium will try to extract the main Activity from your apk. If this fails, you can supply your own with these options.
- **chromeOptions** : additional chromedriver options you can supply.
- **otherApps** : a JSON array of other apps that need to be installed, in URL format.

**For Android & iOS:**
- **locale** : the language of the simulator/device (ex: `fr_CA`) 

This sets the locale on the entire device/simulator, except on physical iOS devices. For real iOS devices, we will pass `-AppleLocale` as argument to the app.

- **newCommandTimeout** : How long (in seconds) Appium will wait for a new command from the client before assuming the client quit and ending the session

### Looking for more help?

Have questions or need more information? Reach out via email or Slack.

[Email us](https://testingbot.com/contact/new)[Slack Join our Slack](https://join.slack.com/t/testingb0t/shared_invite/zt-3bcw9xch-jk19~6XPs_xBrsAgAedkCw)
