.st0{fill:#FFFFFF;}

How to Switch Your iOS App Delegate for Improved Testing 

 March 17, 2015

by Jon Reid

69 COMMENTS

The biggest benefit of test-driven development (TDD) is fast feedback. And so one of the best ways to keep your TDD effective is to make sure you get that feedback as fast as you can.

But most folks use their regular iOS app delegate when testing. This is a problem.

That‘s because when you run your tests, it first fires up your app — which probably does a lot. A bunch of slow stuff. And that's a bunch of slow stuff we don't need or want for our tests.

How can we avoid this?

[This post is part of the series TDD Sample App: The Complete Collection …So Far]

The Testing Sequence

Apple used to segregate unit tests into two categories: application tests and logic tests. The distinction was important because in the bad old days, application tests could only run on device, unless you used a completely different third-party testing framework.

But this distinction diminished when Apple made it possible to run application tests on the simulator. It took a while for their documentation to catch up, but in their latest Testing with Xcode, Apple now talks about “app tests” and “library tests.” This simplifies things down to whether you’re writing an app or writing a library. And Xcode sets up a test target for you that is usually what you need.

If I’m writing an app (or a library that requires a running app), I’m always to going to run the app tests anyway, so I stopped trying to distinguish between the two types of tests. But because Xcode executes app tests within the context of a running app, the testing sequence goes like this:

  1. Launch the simulator
  2. In the simulator, launch the app
  3. Inject the test bundle into the running app
  4. Run the tests

What can we do to make this faster? We can make step 2, “launch the app,” as fast as possible.

The Normal iOS App Delegate

In production code, launching an app may fire off a number of tasks. Ole Begemann’s post Revisiting the App Launch Sequence on iOS goes into more detail, but basically, UIApplicationMain() eventually calls your UIApplicationDelegate subclass to invoke application(:didFinishLaunchingWithOptions:). What that does depends on your app, but it's not unusual for it to do things like:

  • Set up Core Data.
  • Configure the root view controller.
  • Check network reachability.
  • Send a network request to some server to retrieve the latest configuration, such as what to show in the root view controller.

That’s a lot of stuff to do before we even start testing. Wouldn’t it be better to not bother, if all we want is to run our tests?

Let’s set things up that way. Here’s how.

Providing TestingAppDelegate

What if we can use a different application delegate for tests? What would it look like? Let’s go ahead and create it. Make sure it goes into your test target, not your app target.

Note: My instructions below cover projects that don’t use Scene Delegates. For projects that do, see How to Unit Test Scene Delegates when Swift Won’t Let You. Another good article worth checking out is How to Switch Your iOS App and Scene Delegates for Improved Testing by Geoff Hackworth.

  • Swift

  • Objective-C

TestingAppDelegate.swift (link to Gist):

import UIKit
@objc(TestingAppDelegate)
class TestingAppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?
}

Add or Change main

Now let’s change the main entry point, which currently uses AppDelegate every time, explicitly or implicitly. Let’s have it do a dynamic lookup for TestingAppDelegate (which only exists in test code). If it’s there, let’s use that. Otherwise, let’s use the regular AppDelegate.

  • Swift

  • Objective-C

First, remove the @main annotation from your AppDelegate.swift.

Then create a new file main.swift (link to Gist):

import UIKit
let appDelegateClass: AnyClass =
    NSClassFromString("TestingAppDelegate") ?? AppDelegate.self
UIApplicationMain(
    CommandLine.argc,
    CommandLine.unsafeArgv,
    nil,
    NSStringFromClass(appDelegateClass)
)

Bare Bones for Fast Feedback

The important thing is that we've reduced the “launch the app’ step of testing to a bare minimum. There‘s still some overhead, but not much. This is an important step for fast feedback so that we can get the most from TDD.

We've reduced the "launch the app" step of testing to a bare minimum.

Click to Tweet

Even if you‘re starting a brand-new project, I recommend taking this step early because your real iOS app delegate will eventually grow. Let‘s nip that in the bud, and keep the feedback fast.

Another benefit is that now we can write unit tests against the production app delegate, with complete control over what parts of it are exercised, and when. It‘s win-win!

Have you used this technique, or something different? You can share your thoughts in the comments below.

I describe this technique more fully in chapter 4 of my book, iOS Unit Testing by Example: XCTest Tips and Techniques Using Swift.

Jon Reid

About the author

Programming was fun when I was a kid. But working in Silicon Valley, I saw poor code lead to fear, with real human costs. Looking for ways to make my life better, I learned about Extreme Programming, including unit testing, test-driven development (TDD), and refactoring. Programming became fun again! I've now been doing TDD in Apple environments for 20 years. I'm committed to software crafting as a discipline, hoping we can all reach greater effectiveness and joy. Now a coach with Industrial Logic!

  • Nice! I was using a more complex mechanism to identify if the app is running tests:

    - (BOOL)isRunningTests
    {
        NSDictionary *environment = [[NSProcessInfo processInfo] environment];
        NSString *injectBundle = environment[@"XCInjectBundle"];
        return [[[injectBundle lastPathComponent] stringByDeletingPathExtension] isEqualToString:@"MyTargetTests"];
    }
    

    Also, I’m not doing the logic on main but instead on didFinishLaunchingDirectly so I have a if to check if it’s not running tests and only in that case I run all the necessary bootstrap.

  • Great article, Jon. Thanks.

    I have still been using a combination of both Application and Library tests for testing applications, but I’m going to switch over to this approach. Thanks for sharing it with us.

  • Nice of you to talk about this topic, is super important.

    I’ve been using this works with old and new Xcodes, and works with Travis CI

    + (BOOL)isUnitTesting
    {
        NSDictionary *environment = [NSProcessInfo processInfo].environment;
        NSString *serviceNameValue = environment[@"XPC_SERVICE_NAME"];
        NSString *injectBundleValue = environment[@"XCInjectBundle"];
        BOOL runningOnTravis = (environment[@"TRAVIS"] != nil);
        return ([serviceNameValue.pathExtension isEqualToString:@"xctest"] ||
                [serviceNameValue isEqualToString:@"0"] ||
                [injectBundleValue.pathExtension isEqualToString:@"xctest"] ||
                [injectBundleValue isEqualToString:@"0"] ||
                runningOnTravis);
    }
    

    You put it in your AppDelegate and boom! Works!

    One downside is that it doesn’t work with storyboards, weird.

    https://github.com/hyperoslo/NSObject-HYPTesting

    • I used to have calls to some kind of +isUnitTesting method, sprinkled here and there. Sometimes this is an okay starting point with legacy code, but it quickly created its own pain. I’d rather move to a single switch, moved as far outside as I can.

  • IMO, the easiest and more correct-ish way is to create separated target for testing.
    This approach has at least two advantages:

    – the ‘main’ target is clean and know nothing about tests
    – you don’t have to compile parts that you’re not going to test (e.g. some view controllers, views, whatnot)

        • That’s an interesting approach Alex.

          How do you feel about having to add the classes under test, and their dependencies into the test target as well? I personally don’t like having to tick those checkboxes every time.

          Also why do you think your approach highlights components that can be decoupled?

    • I was going to say the same thing. I have separate targets for the app and for testing. That Way I just have to include the AppDelegate with nothing on it on my test target and the regular one on the main target.

      It’s also easier to have dependencies that are needed just for testing on my Podfile this way.

  • This is great John – Thanks.

    We’re using swift here so I’ve had quick go at implementing the same idea:

    1: Comment out @UIApplicationMain in AppDelegate.swift

    2: Make your empty TestingAppDelegate.swift

    import UIKit
    class TestingAppDelegate: UIResponder, UIApplicationDelegate {
        var window: UIWindow?
    }
    

    3: Make a new file called ‘main.swift’ and add the following code:

    import Foundation
    import UIKit
    let isRunningTests = NSClassFromString("XCTestCase") != nil
    if isRunningTests {
        UIApplicationMain(C_ARGC, C_ARGV, nil, NSStringFromClass(TestingAppDelegate))
    } else {
        UIApplicationMain(C_ARGC, C_ARGV, nil, NSStringFromClass(AppDelegate))
    }
    
    • Thanks Paul Booth, you’ve put me on right track with ‘main.swift’ tip.

      Some things have changed in Xcode 7/Swift 2. Following is what I’ve ended up with. Turns out you can just pass nil and skip creating empty TestDelegate.

      import UIKit
      private func delegateClassName() -> String? {
          return NSClassFromString("XCTestCase") == nil ? NSStringFromClass(AppDelegate) : nil
      }
      UIApplicationMain(Process.argc, Process.unsafeArgv, nil, delegateClassName())
      
  • Nice shot. we are actually making return YES early in the didFinishLaunching.

    Does the TestingAppDelegate needs to be compiled into the app ?

    • I used to use an early return in didFinishLaunching. But I found that by moving the test outside to main, it simplified the app delegate — and many more people work in the app delegate, while nobody really touches main. So it’s harder for people to accidentally break. But also, this makes it possible to write unit tests against your app delegate!

      Yes, TestingAppDelegate does need to be compiled into the app. That’s a little smelly, but avoiding the need to link makes the code more complicated. I’d rather go with simple code.

      • Hey Jon, thanks for sharing this. I’ve been using early returns so far too, but I prefer this approach. We can look at it from the SRP point of view, and the AppDelegate should be responsible to find out whether it’s running for testing or not. I’ll convert all my current tests suites now :)

        • Hi Jon, I didn’t get your reply by email (I wish I had).

          To no more have to have “two” delegate compiled :

              @autoreleasepool {
                  Class appDelegateClass = NSClassFromString(@"XYZTestingAppDelegate");
                  if( appDelegateClass == nil ) {
                      appDelegateClass = [DOAAppDelegate class];
                  }
                  return UIApplicationMain(argc, argv, nil, NSStringFromClass(appDelegateClass));
              }
          

          So I prefer this one, as the XYZTestingAppDelegate is no part of the test bundle.
          @Gio : I think the SRP is more respected this way : one AppDelegate for App, without any relationship/test for returning earlier because of tests.

  • Couple things to note from doing this myself:

    – If you’re using storyboards, you’ll need to make sure that your storyboard is not set to load automatically when the app starts up.
    – If you’re using KIF tests or other UI tests, make sure that you’re checking for the existence of those classes in main before you swap out the app delegate. I generally keep my UI tests in a separate bundle from my non-UI tests, so I was able to keep the startup check in main.

    If you want to take a look at how this is set up, I got it working in the TestingPlayground app I put together for my most recent talk:
    https://github.com/designatednerd/TestingPlayground

  • Why not to use for example UIResponder class instead of adding TestingAppDelegate?

    Class appDelegateClass = isRunningTests ? [UIResponder class] : [AppDelegate class];
    
      • UIResponder could be like a stub for AppDelegate.
        If we do not use our AppDelegate like a singleton in a production code, tests won’t call it and this means that we are able to stub delegate with an instance of any class (I’m using UIResponder).

        And there are no need to create fake AppDelegate in production or test target.

        Fake AppDelegate is a must only if our tested production code doing something like this:

        [[[UIApplication sharedApplication]delegate] doSomething];
        
  • This code work form me (little adjusments) in main.swift:

    let isRunningTests = NSClassFromString("XCTestCase") != nil
    if isRunningTests {
        UIApplicationMain(Process.argc, Process.unsafeArgv, nil, NSStringFromClass(TestingAppDelegate))
    } else {
        UIApplicationMain(Process.argc, Process.unsafeArgv, nil, NSStringFromClass(AppDelegate))
    }
    
  • As recently observed, testing for the injected delegate does not work as of Xcode 7 beta 6.

    However, I’ve addressed the problem similarly as the first commenter, by simply testing for the presence of an environment variable. Furthermore, I’m not even providing a substitute delegate; just passing nil as the last parameter to UIApplicationMain works fine.

    int main(int argc, char *argv[])
    {
        NSString *appDelegateClassName = nil;
        
        if (![NSProcessInfo processInfo].environment[@"XCTestConfigurationFilePath"])
        {
            appDelegateClassName = NSStringFromClass([KashooAppDelegate class]);
        }
        return UIApplicationMain(argc, argv, nil, appDelegateClassName);
    }
    
  • I suspect this also has something to do with the strange memory errors I’m seeing when running my existing unit tests that were fine before Xcode 7, in terms of the test bundle being injected “too late”.

  • There’s always the possibility of creating a blank class at runtime:

    #import 
    static void ApplicationDidFinishLaunching(id self, SEL _cmd)
    {
        NSLog(@"TestingAppDelegate finished launching!");
    }
    static UIWindow *window()
    {
        UIWindow *window = [[UIWindow alloc] initWithFrame:CGRectZero];
        window.rootViewController = [[UIViewController alloc] init];
        return window;
    }
    static Class createTestingAppDelegate()
    {
        Class class = objc_allocateClassPair([NSObject class], "TestingAppDelegate", 0);
        class_addMethod(class, @selector(applicationDidFinishLaunching:), (IMP)ApplicationDidFinishLaunching, NULL);
        class_addMethod(class, @selector(window), (IMP)window, "@@:");
        objc_registerClassPair(class);
        return class;
    }
    
    int main(int argc, char *argv[])
    {
        @autoreleasepool {
            BOOL isTesting = NSClassFromString(@"XCTestCase") != nil;
            Class appDelegateClass = (isTesting) ? createTestingAppDelegate() : [AppDelegate class];
            return UIApplicationMain(argc, argv, nil, NSStringFromClass(appDelegateClass));
        }
    }
    
  • In my case under Xcode 7 I had to add the AppDelegateForTesting to the project target since NSClassFromString always returned nil when AppDelegateForTesting was added to the test target.
    Here is the code that worked for me:

    Class appDelegateClass = [AppDelegate class];
    BOOL isRunningTests = ([NSProcessInfo processInfo].environment[@"XCTestConfigurationFilePath"] != nil);
    if (isRunningTests) {
        appDelegateClass = NSClassFromString(@"AppDelegateForTesting");
    }
    return UIApplicationMain(argc, argv, nil, NSStringFromClass(appDelegateClass));
    
    • This is exactly the approach I used to take before learning about testing for classes injected via the test bundle. Now that injection is delayed on Xcode 7, we need to go back to the technique you show.

  • Hi there. This is not working for me. I get crashes because ‘Default context is nil! Did you forget to initialize the Core Data Stack?’ is now missing from the AppDelegate

    • I agree with Kenji that Core Data initialization should be handled elsewhere. And for unit tests, you’ll probably want an in-memory stack that you configure for the tests.

      But that aside… the basic idea of having a TestingAppDelegate is that you can customize it if necessary, mimicking any “must-haves” from your real app delegate.

  • Found the solution (thanks @Simon Lucas to give me the idea to look into env vars) :

    NSString *appDelegateClassName = NSStringFromClass([AppDelegate class]);
    NSString *testBundlePath = [NSProcessInfo processInfo].environment[@"TestBundleLocation"];
    if( testBundlePath )
    {
        NSBundle *testBundle = [NSBundle bundleWithPath:testBundlePath];
        NSError *error = NULL;
        [testBundle loadAndReturnError:&error];
        if( error )
        {
            NSLog(@"error : %@", error);
        }
        appDelegateClassName = @"XYZTestingAppDelegate";
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
    

    I think this solution is better than relying on runtime object creation.

    • Arg… work only in simulator.
      On a device :

      (lldb) print (BOOL) [[NSFileManager defaultManager] fileExistsAtPath:testBundlePath]
      (BOOL) $2 = NO
      
      • TestBundleLocation is location of the xctest bundle on the Mac side, not device one.

        So I fixed it as the bundle is in the tmp directory of the app :

            @autoreleasepool {
                NSString *appDelegateClassName = NSStringFromClass([AppDelegate class]);
                NSString *testBundlePath = [NSProcessInfo processInfo].environment[@"TestBundleLocation"];
        #if !TARGET_IPHONE_SIMULATOR
                NSString *testBundleName = [testBundlePath lastPathComponent];
                testBundlePath = [[NSTemporaryDirectory() stringByAppendingPathComponent:testBundleName] stringByStandardizingPath];
        #endif
                if( testBundlePath )
                {
                    NSError *error = nil;
                    NSBundle *testBundle = [NSBundle bundleWithPath:testBundlePath];
                    [testBundle load];
                    if( testBundle == nil || error )
                    {
                        NSLog(@"error loading bundle %@ : %@", testBundle, error);
                    }
                    appDelegateClassName = @"MBCTestingAppDelegate";
                }
                return UIApplicationMain(argc, argv, nil, appDelegateClassName);
            }
        

        Tested on two devices (8.4 and 9.1)

  • If you want to omit the default view controller too, you can do that by providing it in the fake app delegate

    class TestingAppDelegate: UIResponder, UIApplicationDelegate {
      var window: UIWindow? = UIWindow(frame: UIScreen.mainScreen().bounds)
      func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        window?.rootViewController = UIViewController()
        return true
      }
    }
    
  • Love this approach, but do you know of any technique for detecting if we are running UI tests? As in this scenario we would want to load the original app delegate (the one that actually has the UI to test).

    • The only way I know of is less automatic. Make sure your UI tests scheme sets up an environment variable. Then in main, test for this variable with getenv.

    • Nice blog post Andy, thanks for sharing! You’re absolutely right, the coverage stats would be skewed by the normal app launch sequence — something that doesn’t reflect the test content in any way. We reached the same conclusion, for different purposes.

  • Hello, so what is the current working and preferred approach for testing AppDelegate? I’ve read that the approach described in the beginning in this blog does not work anymore. Or does it still work?
    I’m currently trying to set up my testing environment based on Kiwi. I quickly learned that accessing AppDelegate directly in tests via [[UIApplication sharedApplication] delegate] does not give me enough control. So now I’m looking for any kind of ways to isloate AppDelegate for testing.

  • I think theres a mistake in your post. In the section “Provide TestingAppDelegate” you just have the main.swift code and not a TestAppDelegate.swift file.

    • Oh I see, I mistakenly repeated the main.swift part. Thanks. I also updated main.swift for Xcode 8 beta, which currently complains about Process.unsafeArgv unless it has those crazy casts.

      • As part of the mass Swift renaming, etc. in Swift 3 Xcode Beta 6, Process.argc and Process.unsafeArgv have been renamed to CommandLine.argc and CommandLine.unsafeArgv (link), and the UnsafeMutablePointer ‘init’ has been replaced ‘withMemoryRebound(to:capacity:_)’ (link). Still trying to understand what that means for the code in main.swift, but know it won’t work until fixed.

  • This seems to work in main.swift for Xcode 8 beta 6.

    let isRunningTests = NSClassFromString( "XCTestCase" ) != nil
    let appDelegateClass: AnyClass = isRunningTests ? TestingAppDelegate.self : AppDelegate.self
    let argsCount = Int( CommandLine.argc )
    let argsRawPointer = UnsafeMutableRawPointer( CommandLine.unsafeArgv )
    let args = argsRawPointer.bindMemory( to: UnsafeMutablePointer<Int8>.self, capacity: argsCount )
    UIApplicationMain( CommandLine.argc,
                       args,
                       nil,
                       NSStringFromClass( appDelegateClass )
    )
    
  • Nice idea, I do this:

      var testing: Bool {
        if let _ = NSClassFromString("XCTestCase") {
          return true
        } else {
          return false
        }
      }
      func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        // app init
        // required init
        guard testingMode() == false else {
          return true
        }
        // regular init
        return true
      }
    
  • Hi Jon,

    This solution is pretty cool and easy to follow. I tried it in my sample app and it runs perfectly fine.

    The same solution doesn’t work on other projects and it’s a large codebase. Multiple targets and schemes. I run a test and found that AppDelegate found nil in XCTest

    func testApp() {
        let controller = UIViewController()
        controller.loadViewIfNeeded()
        let view = controller.view!
        let frame = CGRect(x: 0, y: 0, width: 200, height: 40)
        let textFieldContainer = UIKit.UIView(frame: frame)
        let textField = UITextField(frame: frame)
        textFieldContainer.addSubview(textField)
        view.addSubview(textFieldContainer)
    
        window.addSubview(view)
        window.isHidden = false
        window.rootViewController = controller
        window.makeKeyAndVisible()
    
        XCTAssertNotNil(UIApplication.shared.delegate)
        XCTAssertNotNil(UIWindow.orientation())
        XCTAssertNotNil(UIWindow.statusBarFrame())
    }
    
  • {"email":"Email address invalid","url":"Website address invalid","required":"Required field missing"}
    >

    Get FREE SNIPPETS for unit testing!