Skip to content
Jeffrey Wear edited this page May 1, 2014 · 6 revisions

When you installed Subliminal, it added a "Subliminal test" file template to Xcode. You can use this template to add tests to your project. Choose your Integration Tests group, choose File > New > File…, select the Subliminal group from the sidebar, and choose the Integration Test Case. When you add the file, make sure to link it to your Integration Tests target.

This will create an empty SLTest subclass. Like with OCUnit and XCTest, test cases must start with the word test, take no arguments, and return void. Here's a simple test case:

@implementation STLoginTest

- (void)testLogInSucceedsWithUsernameAndPassword {
	SLTextField *usernameField = [SLTextField elementWithAccessibilityLabel:@"username field"];
	SLTextField *passwordField = [SLTextField elementWithAccessibilityLabel:@"password field" isSecure:YES];
	SLElement *submitButton = [SLElement elementWithAccessibilityLabel:@"Submit"];
	SLElement *loginSpinner = [SLElement elementWithAccessibilityLabel:@"Logging in..."];
	
    NSString *username = @"Jeff", *password = @"foo";
    [usernameField setText:username];
    [passwordField setText:password];

    [submitButton tap];

	// wait for the login spinner to disappear
    SLAssertTrueWithTimeout([loginSpinner isInvalidOrInvisible], 
    						3.0, @"Log-in was not successful.");

    NSString *successMessage = [NSString stringWithFormat:@"Hello, %@!", username];
    SLAssertTrue([[SLElement elementWithAccessibilityLabel:successMessage] isValid], 
    			@"Log-in did not succeed.");

    // Check the internal state of the app.         
    SLAssertTrue(SLAskAppYesNo(isUserLoggedIn), @"User is not logged in.")
}

@end

In the body of those tests, you do some work and then make some assertions. That "work" often takes the form of simulating user interaction and/or manipulating the application directly.

Simulate User Interaction

In Subliminal tests, you manipulate the user interface using instances of SLElement, which are located by their accessibilityLabel or accessibilityIdentifier properties. SLElements are proxies for the "UIAElements" UIAutomation uses to represent user interface elements: when you -tap an SLElement, that SLElement causes the appropriate bit of JavaScript to be executed to manipulate the corresponding UIAElement. Tests execute asynchronously, so they can block until UIAutomation is done evaluating the command.

Manipulate the Application Directly

Subliminal lets tests access and manipulate application state by using "app hooks". Hooks are methods which the application registers with the test controller to then be invoked, by name, by the tests.

For instance, before running the tests, the application delegate could register a "login manager" singleton as being able to programmatically log a test user in:

[[SLTestController sharedTestController] registerTarget:[LoginManager sharedManager] 
                                               forAction:@selector(logInWithInfo:)];

When tests need to log in, they could then call loginWithInfo::

[[SLTestController sharedTestController] sendAction:@selector(logInWithInfo:)
                                         withObject:@{
                                                        @"username": @"john@foo.com",
                                                        @"password": @"Hello1234"
                                                     }];

App hooks have several benefits:

  • they help developers write independent tests: only one test need evaluate the login UI, while the others can use the programmatic interface
  • they let test writers re-use their application's code without making their tests dependent on the application's structure
  • they keep the application and test from sharing state, by passing arguments and return values by copy
  • they permit the application and tests to communicate in a thread-safe manner, by performing the "hooked" method on the main thread

If you choose to call application APIs directly (rather than use app hooks), remember that tests run in a background thread: you should wrap calls in dispatch_sync(dispatch_get_main_queue, ^{...}).