Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

iOS UI Testing On an Isolated View

I'm trying to incorporate UI tests in my iOS project, but one thing that continues to hold me up is the fact that it seems all of the tests you write must start from the beginning of the app and work their way through. For example, if I want to test a view that is behind a login screen, my tests must first run on the login screen, enter a username/password, click login, then go to the view I want to test. Ideally, the tests for the login view and the next one would be completely isolated. Is there a way to do this, or am I missing the philosophy behind UI tests completely?

like image 630
mike Avatar asked Dec 17 '15 17:12

mike


People also ask

How do I test my iOS UI?

Recording a UI Test From the debug bar, click the Record UI Test button. Xcode will launch the app and run it. You can interact with the element on-screen and perform a sequence of interactions for any UI test. Whenever you interact with an element, Xcode writes the corresponding code for it into your method.

How should UI tests be tested?

UI testing is centered around two main things. First, checking how the application handles user actions carried out using the keyboard, mouse, and other input devices. Second, checking whether visual elements are displayed and working correctly.


2 Answers

Absolutely!

What you need is a clean application environment in which you can run your tests - a blank slate.

All applications have an application delegate which sets up the initial state of the application and provides a root view controller on launch. For the purposes of testing you don't want that to happen - you need to be able to test in isolation, without all of those things happening. Ideally you want to be able to have the screen undertest and only that screen loaded, and no other state changes happen.

To do so you can create an object just for testing that implements UIApplicationDelegate. You can tell the application to run in "testing mode" and use the testing-specific application delegate using a launch argument.

Objective-C: main.m:

int main(int argc, char * argv[]) {
NSString * const kUITestingLaunchArgument   = @"org.quellish.UITestingEnabled";

    @autoreleasepool {
        if ([[NSUserDefaults standardUserDefaults] valueForKey:kUITestingLaunchArgument] != nil){
            return UIApplicationMain(argc, argv, nil, NSStringFromClass([TestingApplicationDelegate class]));
        } else {
            return UIApplicationMain(argc, argv, nil, NSStringFromClass([ProductionApplicationDelegate class]));
        }
    }
}

Swift: main.swift:

let kUITestingLaunchArgument = "org.quellish.UITestingEnabled"

if (NSUserDefaults.standardUserDefaults().valueForKey(kUITestingLaunchArgument) != nil){
    UIApplicationMain(Process.argc, Process.unsafeArgv, NSStringFromClass(UIApplication), NSStringFromClass(TestingApplicationDelegate))

} else {
    UIApplicationMain(Process.argc, Process.unsafeArgv, NSStringFromClass(UIApplication), NSStringFromClass(AppDelegate))
}

You will have to remove any @UIApplicationMain annotation from your Swift classes.

For "application tests" be sure to set the "Test" action of the scheme in Xcode to provide the launch argument:

Xcode Scheme editor

For UI tests you can set the launch arguments as part of the test:

Objective-C:

XCUIApplication *app = [[XCUIApplication alloc] init];
[app setLaunchArguments:@[@"org.quellish.UITestingEnabled"] ];
[app launch];

Swift:

let app = XCUIApplication()
app.launchArguments = [ "org.quellish.UITestingEnabled" ]
app.launch()

This allows the tests to use an application delegate specificly for testing. This empowers you with a lot of control - you now have a blank slate to work with for testing. The testing application delegate can load a specific storyboard or put in place an empty UIViewController. As part of your UI tests you might instantiate the view controller under test and set it as the keyWindow's root view controller or present it modally. Once it has been added or presented your tests can execute, and when complete remove or dismiss it.

like image 196
quellish Avatar answered Oct 01 '22 08:10

quellish


If you don't mind the original UI loading, just jump to the target UI with:

override func setUp() {
    super.setUp()
    continueAfterFailure = false
    XCUIApplication().launch()
    let storyboard = UIStoryboard(name: "MainStoryboard", bundle: NSBundle.mainBundle())
    let controller = storyboard.instantiateViewControllerWithIdentifier("LanguageSelectController")
    UIApplication.sharedApplication().keyWindow?.rootViewController = controller
}

If you do not want the original UI underneath to load then also pass in this from your test:

app.launchArguments.append("skipEntryViewController")

and then in didFinishLaunchingWithOptions, you can check:

if NSProcessInfo.processInfo().arguments.contains("skipEntryViewController") {
    // then do NOT call makeKeyAndVisible
}
like image 20
William Entriken Avatar answered Oct 01 '22 09:10

William Entriken