Xcode Automated UI Tests & NSUserDefaults

For an application I'm building, I use NSUserDefaults the app's desired initial screen. If it's the first launch, they should see the app's explainer slideshow. If they've seen the slideshow, the app takes them to the account creation screen. And if they've logged in before, the first thing they see is the login screen.

Everything works great. But now I want to write some automated UI tests.

The first time the tests run, everything is fine. But on subsequent runs, the app is starting on a different screen, which of course causes the tests to fail. What I really want to do is clear the NSUserDefaults every time a UI test runs.

Seems like something you'd want in the setUp method of your UI test, yeah?

import <XCTest/XCTest.h>
@implementation UITests

- (void)setUp {
    [super setUp];

    //clear all user defaults
    NSString *appDomain = [[NSBundle mainBundle] bundleIdentifier];
    [[NSUserDefaults standardUserDefaults] removePersistentDomainForName:appDomain];

    //launch
    [[[XCUIApplication alloc] init] launch];
}

- (void)testMyUIStuff {
    ...
}

But this doesn't work. You apparently can't update standardUserDefaults in an XCTest and have the application see the changes.

What does work, though, is to just clear out the defaults in the applicationDidFinishLaunching method of my AppDelegate class.

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
    [self clearUserDefaults];
    
    return YES;
}

But the issue now is that user defaults get cleared every time the app launches, and not just when it's launched through a UI test. Obviously not what we want.

The solution I found was to write to the launchArguments property of my XCUIApplication instance in my test class, and then read that parameter in my AppDelegate class. 

UITests.m:

- (void)setUp {
    [super setUp];

    XCUIApplication *app = [[XCUIApplication alloc] init];
    app.launchArguments = @[@"isUITesting"];
    [app launch];
}

AppDelegate.m:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
    //ui testing setup
    if ([[[NSProcessInfo processInfo] arguments] containsObject:@"isUITesting"]) {
        [self clearUserDefaults];
    }
    
    return YES;
}

- (void)clearUserDefaults {
    NSString *appDomain = [[NSBundle mainBundle] bundleIdentifier];
    [[NSUserDefaults standardUserDefaults] removePersistentDomainForName:appDomain];
}

Now my UI tests pass consistently, my application runs properly through normal use, and life is good.