How to Easily Switch Your App Delegate for Testing

March 17, 2015 — 67 Comments

October 25, 2016 Update: Now able to put TestingAppDelegate in test target, even in Swift

The biggest benefit of Test Driven Development 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 iOS folks use their production 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 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 the app delegate to have it execute 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.

Change main

Let’s change our main function, which looks like this in Objective-C:

#import <UIKit/UIKit.h>
#import "AppDelegate.h"

int main(int argc, char *argv[])
{
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

We now want it to check if we are running tests. And if so, we want it to use a different app delegate. We can do that like this:

For Objective-C: Put the TestingAppDelegate in the test target. If we can load it, we use it.

#import <UIKit/UIKit.h>
#import "AppDelegate.h"

int main(int argc, char *argv[])
{
    @autoreleasepool {
        Class appDelegateClass = NSClassFromString(@"TestingAppDelegate");
        if (!appDelegateClass)
            appDelegateClass = [AppDelegate class];
        return UIApplicationMain(argc, argv, nil, NSStringFromClass(appDelegateClass));
    }
}

For Swift: First, remove the @UIApplicationMain annotation from AppDelegate.swift. Then create a new file main.swift:

import UIKit

let appDelegateClass: AnyClass? =
    NSClassFromString("TEST_TARGET.TestingAppDelegate") ?? AppDelegate.self
let args = UnsafeMutableRawPointer(CommandLine.unsafeArgv)
    .bindMemory(to: UnsafeMutablePointer<Int8>.self, capacity: Int(CommandLine.argc))
UIApplicationMain(CommandLine.argc, args, nil, NSStringFromClass(appDelegateClass!))

replacing TEST_TARGET with the name of the target containing TestingAppDelegate.

Provide TestingAppDelegate

This requires creating a new TestingAppDelegate class. In Objective-C, it’s nothing more than this:

TestingAppDelegate.h

#import <UIKit/UIKit.h>

@interface TestingAppDelegate : NSObject
@property (nonatomic, strong) UIWindow *window;
@end

TestingAppDelegate.m

#import "TestingAppDelegate.h"

@implementation TestingAppDelegate
@end

(In earlier versions of iOS, I had to add more code so that the TestingAppDelegate would create a window, give that window a do-nothing root view controller, and make the window visible. It looks like that’s no longer necessary.)

For Swift, it’s simply:

TestingAppDelegate.swift

import UIKit

class TestingAppDelegate: NSObject {
    var window: UIWindow?
}

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.

Even if you’re starting a brand-new project, I recommend taking this step early because your real 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 by clicking here.

This post is part of a series on What Is the Most Important Benefit of TDD? Did you find it useful? Subscribe today to get regular posts on clean iOS code.

Jon Reid

Posts Twitter Facebook Google+

I've been practicing Test Driven Development (TDD) since 2001. Learn more on my About page.

67 responses to How to Easily Switch Your App Delegate for Testing

  1. 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.

  2. 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.

  3. 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.

  4. 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)

    • So do you put the code you want to test into two targets: your app, and your test target (where your test target doesn’t link against your app)?

      • Yes, some parts included in both targets. Also, this approach shows which parts could be completely decoupled from the codebase into ‘submodules’.

        • 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.

  5. 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! There was a discussion on Twitter about “How do I do that in Swift?” so it’s good to have an example.

    • 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())
  6. 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.

  7. 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

  8. Awesome stuff! Love your blog.

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

    Class appDelegateClass = isRunningTests ? [UIResponder class] : [AppDelegate class];
    • Because UIResponder will always be present. But see this comment for a better way.

      • 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];
  10. 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))
    }

  11. This technique fails for me in Xcode 7 beta, it is no longer finding the TestAppDelegate. Any pointers how to solve this?

  12. 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);
    }
  13. Oops, I forgot to close a tag properly up there. Oh well.

  14. 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”.

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

    #import <objc/runtime.h>
    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));
        }
    }
  16. 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.

  17. 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

    • Core data initialization has nothing to do in your app delegate (put this in another object, more respectful to the SRP).

    • 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.

  18. 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:&amp;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)

      • I run unit tests only on simulator.

  19. I just updated the post for what I use now in Xcode 7.

  20. 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
      }
    }
  21. 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.

  22. This approach also has the added benefit of fixing some holes in the default way that Xcode calculates code coverage. I wrote about on my blog: http://cleanswifter.com/broken-code-coverage-xcode-fix/

    • 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.

  23. 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.

  24. 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.

  25. 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 )
    )
  26. Hugues Bernet-Rollande October 18, 2016 at 8:31 am

    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
      }
  27. In case anyone is interested in Mac development, the principle applies, but the implementation details are a little different. I wrote up a short blog post with the specifics.

Leave a Reply

Text formatting is available via select HTML.

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> 

*