# iOS native app automation testing

{% hint style="info" %}
Appfigurate doesn't currently support automation testing watchOS, Flutter or React Native apps.
{% endhint %}

Appfigurate can change the configuration of an iOS app being automation tested using `XCTestCase`.

## Examples

An example UI testing bundle `AppfigurateExampleUITests` is available in both [Objective-C example](https://docs.electricbolt.co.nz/getting-started/examples#objective-c-example) and [Swift example](https://docs.electricbolt.co.nz/getting-started/examples#swift-example) GitHub repositories. To test, ensure `AppfigurateExample (ObjC/Swift iOS)` is selected as the scheme, then long tap the run button to show more options and tap `Test` (⌘U).

<img src="https://1008176080-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fw1fcw3dvtSrfUh3YtO9Z%2Fuploads%2Ftr2Yu6H0BzPSSptrQDU1%2FAppfigurateExampleUITestsSchemeTest.png?alt=media&#x26;token=4eefab59-8738-42f7-b68f-fd9eac8a085c" alt="" data-size="original">

## Testing your app

### Add APLConfiguration subclass to UI testing bundle

Tap on your [<mark style="color:blue;">`APLConfiguration`</mark>](https://www.electricbolt.co.nz/api/Classes/APLConfiguration.html) subclass in the project navigator. In File inspector ‣ Target membership, tick on your UI testing bundle.

![](https://1008176080-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fw1fcw3dvtSrfUh3YtO9Z%2Fuploads%2F7ts8cFP0fm9DBpZXPtX4%2FAppfigurateExampleUITestsTarget.png?alt=media\&token=1fb6280f-3cc9-45aa-9f74-018169f86da6)

### Ensure allowInvalidSignatures returns YES/true

In your [<mark style="color:blue;">`APLConfiguration`</mark>](https://www.electricbolt.co.nz/api/Classes/APLConfiguration.html) subclass, confirm that your [<mark style="color:blue;">`allowInvalidSignatures`</mark>](https://www.electricbolt.co.nz/api/Classes/APLConfiguration.html#/c:objc\(cs\)APLConfiguration\(im\)allowInvalidSignatures) method returns `YES`/`true` when running automation tests. (Test schemes are by default run with a DEBUG build).

{% tabs %}
{% tab title="Swift" %}

```swift
@objcMembers class ExampleConfiguration: APLConfiguration {
...
  override func allowInvalidSignatures() -> Bool {
    return !ENCRYPTED()
  }
...
```

{% endtab %}

{% tab title="Objective-C" %}

```objectivec
@implementation ExampleConfiguration
...
- (BOOL) allowInvalidSignatures {
#if DEBUG
    return YES;
#else
    return NO;
#endif
}
...
```

{% endtab %}
{% endtabs %}

### Add an UIInterruptionMonitor

When the configuration is applied to the application on launch, the standard Appfigurate 'Configuration applied' alert is displayed. It is recommended you add an `UIInterruptionMonitor` to your `XCTestCase` to automatically dismiss this alert. A good place to do this in the `setUp` method.

{% tabs %}
{% tab title="Swift" %}

```swift
addUIInterruptionMonitor(withDescription: "Appfigurate") { (element) -> Bool in
  if (element.elementType == .alert) {
    if (element.buttons["OK"].exists) {
      element.buttons["OK"].tap()
      return true
    } else if (element.buttons["Ignore"].exists) {
      element.buttons["Ignore"].tap()
      return true
    }
  }
  return false
}
```

{% endtab %}

{% tab title="Objective-C" %}

```objectivec
[self addUIInterruptionMonitorWithDescription: @"Appfigurate" handler: ^(XCUIElement *element) {
    if (element.elementType == XCUIElementTypeAlert) {
        if ([element.buttons[@"OK"] exists]) {
            [element.buttons[@"OK"] tap];
            return YES;
        } else if ([element.buttons[@"Ignore"] exists]) {
            [element.buttons[@"Ignore"] tap];
            return YES;
        }
    }
    return NO;
}];
```

{% endtab %}
{% endtabs %}

### Create instance of APLConfiguration subclass

* Get an instance of your [<mark style="color:blue;">`APLConfiguration`</mark>](https://www.electricbolt.co.nz/api/Classes/APLConfiguration.html) subclass in your `XCTestCase`. A good place to do this in the `setUp` method.
* Set the properties required to be applied to your app to allow it to be tested correctly.

{% tabs %}
{% tab title="Swift" %}

```swift
let c = APLConfiguration.shared() as! ExampleConfiguration
c.boolean = false
c.string_Textfield = "thursday"
```

{% endtab %}

{% tab title="Objective-C" %}

<pre class="language-objectivec"><code class="lang-objectivec"><strong>ExampleConfiguration* c = (ExampleConfiguration*) [APLConfiguration sharedConfiguration];
</strong><strong>c.boolean = NO;
</strong><strong>c.string_Textfield = @"thursday";
</strong></code></pre>

{% endtab %}
{% endtabs %}

### Apply configuration to XCUIApplication

* Apply the result of [<mark style="color:blue;">`APLConfiguration`</mark>](https://www.electricbolt.co.nz/api/Classes/APLConfiguration.html) [<mark style="color:blue;">`automationLaunchArguments`</mark>](https://www.electricbolt.co.nz/api/Classes/APLConfiguration.html#/c:objc\(cs\)APLConfiguration\(im\)automationLaunchArguments) method to `XCUIApplication launchArguments` property.
* Launch your app.

{% tabs %}
{% tab title="Swift" %}

```swift
let app = XCUIApplication()
app.launchArguments = c.automationLaunchArguments()
app.launch()
```

{% endtab %}

{% tab title="Objective-C" %}

```objectivec
XCUIApplication* app = [XCUIApplication new];
app.launchArguments = [c automationLaunchArguments];
[app launch];
```

{% endtab %}
{% endtabs %}

Once your app is launched, the configuration you set in [Create instance of APLConfiguration subclass ](#create-instance-of-aplconfiguration-subclass)will be applied before executing each test case.

### Additional automation launch methods

See also [<mark style="color:blue;">`automationLaunchArgumentsReset`</mark>](https://www.electricbolt.co.nz/api/Classes/APLConfiguration.html#/c:objc\(cs\)APLConfiguration\(im\)automationLaunchArgumentsReset) and [<mark style="color:blue;">`automationLaunchArgumentsWithAction`</mark>](https://www.electricbolt.co.nz/api/Classes/APLConfiguration.html#/c:objc\(cs\)APLConfiguration\(im\)automationLaunchArgumentsWithAction) methods of [<mark style="color:blue;">`APLConfiguration`</mark>](https://www.electricbolt.co.nz/api/Classes/APLConfiguration.html).

## Applying configuration at runtime to the app under test

You can apply configuration to the app after it has launched using the [<mark style="color:blue;">`automationSendConfiguration`</mark>](https://www.electricbolt.co.nz/api/Classes/APLConfiguration.html#/c:objc\(cs\)APLConfiguration\(im\)automationSendConfiguration), [<mark style="color:blue;">`automationSendConfigurationReset`</mark>](https://www.electricbolt.co.nz/api/Classes/APLConfiguration.html#/c:objc\(cs\)APLConfiguration\(im\)automationSendConfigurationReset) and [<mark style="color:blue;">`automationSendConfigurationWithAction`</mark>](https://www.electricbolt.co.nz/api/Classes/APLConfiguration.html#/c:objc\(cs\)APLConfiguration\(im\)automationSendConfigurationWithAction:) methods of [<mark style="color:blue;">`APLConfiguration`</mark>](https://www.electricbolt.co.nz/api/Classes/APLConfiguration.html). You can read the configuration from the app under test using the [<mark style="color:blue;">`automationSendReadConfiguration`</mark>](https://www.electricbolt.co.nz/api/Classes/APLConfiguration.html#/c:objc\(cs\)APLConfiguration\(im\)automationSendReadConfiguration) method.

{% tabs %}
{% tab title="Swift" %}

```swift
func testSendConfig() {
  app.launchArguments = c.automationLaunchArgumentsReset()
  app.launch()
  c.boolean = true
  c.automationSendConfiguration()
  ...
```

{% endtab %}

{% tab title="Objective-C" %}

```objectivec
- (void)testSendConfig {
    app.launchArguments = [c automationLaunchArgumentsReset];
    [app launch];
    c.boolean = YES;
    [c automationSendConfiguration];
    ...
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
Under the hood, the [<mark style="color:blue;">`automationSendConfiguration`</mark>](https://www.electricbolt.co.nz/api/Classes/APLConfiguration.html#/c:objc\(cs\)APLConfiguration\(im\)automationSendConfiguration), [<mark style="color:blue;">`automationSendConfigurationReset`</mark>](https://www.electricbolt.co.nz/api/Classes/APLConfiguration.html#/c:objc\(cs\)APLConfiguration\(im\)automationSendConfigurationReset), [<mark style="color:blue;">`automationSendConfigurationWithAction`</mark>](https://www.electricbolt.co.nz/api/Classes/APLConfiguration.html#/c:objc\(cs\)APLConfiguration\(im\)automationSendConfigurationWithAction:) and [<mark style="color:blue;">`automationSendReadConfiguration`</mark>](https://www.electricbolt.co.nz/api/Classes/APLConfiguration.html#/c:objc\(cs\)APLConfiguration\(im\)automationSendReadConfiguration) methods use the [<mark style="color:blue;">`APLAutomationSendMessage`</mark>](https://www.electricbolt.co.nz/api/Functions.html#/c:@F@APLAutomationSendMessage) and [<mark style="color:blue;">`APLAutomationMessageReceivedBlock`</mark>](https://www.electricbolt.co.nz/api/Functions.html#/c:@F@APLAutomationMessageReceivedBlock) functions introduced in the [next section](#invoking-functionality-in-the-app-under-test-at-runtime).
{% endhint %}

## Invoking functionality in the app under test at runtime

Appfigurate allows you to invoke functionality in the app under test, at runtime, from your `XCTestCase`. Example use cases:

* Setting mocked HTTP responses for HTTP requests in the app.
* Share mocked objects between the `XCTestCase` and app.
* Read and set the internal state of the app.

### Instrument the app under test

Use the [<mark style="color:blue;">`APLAutomationMessageReceivedBlock`</mark>](https://www.electricbolt.co.nz/api/Functions.html#/c:@F@APLAutomationMessageReceivedBlock) function in the app under test to declare a callback. The callback will be invoked anytime a message is received from the `XCTestCase`. Best practice is to wrap the function in `#if DEBUG` to ensure it's not included in an App Store build.

{% tabs %}
{% tab title="Swift" %}
App under test example

```swift
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
  APLApplicationDidFinishLaunchingWithOptions(launchOptions)
        
#if DEBUG
  APLAutomationMessageReceivedBlock { message, plist in
    let window = UIApplication.shared.windows.filter {$0.isKeyWindow}.first!
            
    if (message == "SetDarkMode") {
      let bool = plist as! Bool
      window.overrideUserInterfaceStyle = bool ? .dark : .light
    } else if message == "GetDarkMode" {
      let bool = window.traitCollection.userInterfaceStyle == .dark
      return bool
    }
    return nil
  }
#endif

  return true
}
```

{% endtab %}

{% tab title="Objective-C" %}
App under test example

```objectivec
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    APLApplicationDidFinishLaunchingWithOptions(launchOptions);
    
#if DEBUG
    APLAutomationMessageReceivedBlock(^id _Nullable(NSString * _Nonnull message, id  _Nullable plist) {
        UIWindow* window;
        for (window in [UIApplication sharedApplication].windows) {
            if ([window isKeyWindow]) {
                break;
            }
        }
            
        if ([message isEqualToString: @"SetDarkMode"]) {
            window.overrideUserInterfaceStyle = [plist boolValue] ? UIUserInterfaceStyleDark : UIUserInterfaceStyleLight;
        } else if ([message isEqualToString: @"GetDarkMode"]) {
            return [NSNumber numberWithBool: window.traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark];
        }
        return nil;
    });
#endif
    return YES;
}
```

{% endtab %}
{% endtabs %}

### Invoke functionality from your XCTestCase

Use the [<mark style="color:blue;">`APLAutomationSendMessage`</mark>](https://www.electricbolt.co.nz/api/Functions.html#/c:@F@APLAutomationSendMessage) function in your `XCTestCase` to send a message and wait for a reply from the app under test.

{% tabs %}
{% tab title="Swift" %}
XCTestCase example

<pre class="language-swift"><code class="lang-swift"><strong>func testSendMessageToApplicationUnderTest() {
</strong>  app.launchArguments = c.automationLaunchArgumentsReset()
  app.launch()
  APLAutomationSendMessage("SetDarkMode", true, 3.0)
  XCTAssertTrue(APLAutomationSendMessage("GetDarkMode", nil, 3.0) as! Bool)
}
</code></pre>

{% endtab %}

{% tab title="Objective-C" %}
XCTestCase example

```objectivec
- (void)testSendMessageToApplicationUnderTest {
    app.launchArguments = [config automationLaunchArgumentsReset];
    [app launch];
    APLAutomationSendMessage(@"SetDarkMode", @YES, 3.0);
    XCTAssertTrue([APLAutomationSendMessage(@"GetDarkMode", nil, 3.0) boolValue]);
}
```

{% endtab %}
{% endtabs %}

{% hint style="success" %}
The [<mark style="color:blue;">`APLAutomationSendMessage`</mark>](https://www.electricbolt.co.nz/api/Functions.html#/c:@F@APLAutomationSendMessage) and [<mark style="color:blue;">`APLAutomationMessageReceivedBlock`</mark>](https://www.electricbolt.co.nz/api/Functions.html#/c:@F@APLAutomationMessageReceivedBlock) functions are compatible with both iOS Simulators and physical iOS devices.
{% endhint %}

{% hint style="info" %}
The [<mark style="color:blue;">`APLAutomationSendMessage`</mark>](https://www.electricbolt.co.nz/api/Functions.html#/c:@F@APLAutomationSendMessage) `plist` parameter accepts any object that is [property list compatible](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/PropertyLists/Introduction/Introduction.html#//apple_ref/doc/uid/10000048i), or `nil`. This also applies to the result returned from [<mark style="color:blue;">`APLAutomationMessageReceivedBlock`</mark>](https://www.electricbolt.co.nz/api/Functions.html#/c:@F@APLAutomationMessageReceivedBlock). The size of the property list once serialized, must be less than 65535 bytes, otherwise an [<mark style="color:blue;">`AppfigurateLibraryException`</mark>](https://www.electricbolt.co.nz/api/Constants.html#/c:@AppfigurateLibraryException) will be thrown from the `XCTestCase`. In practice, keep your property lists to just a few hundred bytes. Larger property lists will result in significant transmission time (tens of seconds).
{% endhint %}
