Home Articles

Improve Test case execution time by using TestAppDelegate

Let us discuss how to improve the test case execution using TestAppDelegate in this article:

Improve Test case execution

Unit testing should be as fast as possible and anything that slows it down should be removed.

We have a more than 1K test cases in our application. As test cases got increased it slowed down running tests. I had a discussion about this with my colleagues and captured two words from discussion i.e. Set none to host application or use TestAppDelegate . I did research on host application and came to know that it is must to set for application tests. We have a tests in other target i.e. cocoa touch framework(library) that doesn’t need main application as host to run tests and it works perfectly there. Till now host application point is clear.

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

Whenever test case runs, it invokes main.m which invokes appDelegate heavy code. It’s good to use TestAppDelegate when test runs. It should be set up when we start new project to avoid any side effect as app delegate will grow over time. Unfortunately, we are going to use TestAppDelegate now.

Why use TestAppDelegate ?

When iOS launches an app, it needs one of the following things:

  • @UIApplicationMain notation: it’s applied to a class to indicate that it is the application delegate.
  • UIApplicationMain() function: it’s called in the main entry point—which is usually main.swift —to create the application object, the application delegate and set up the event cycle.

By default, an iOS project has a class AppDelegate with the notation @UIApplicationMain . It means that this class is the entry point of your app.

Then, iOS has to find the entry point for the UI. We can use two different ways to load the main UI component: either Using Storyboard or Load Programmatically .

Load programmatically:


    @UIApplicationMain
    class AppDelegate: UIResponder, UIApplicationDelegate {
        var window: UIWindow?
        func application(_ application: UIApplication,
                         didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
            window = UIWindow()
            window?.rootViewController = UIViewController()
            window?.makeKeyAndVisible()
            
            return true
        }
    }

Create A New App Entry Point (main.m)

First of all, we need a new entry point to load either the normal or the test AppDelegate depending on whether the app is launched by unit tests or not.

The entry point of an app can be either an AppDelegate with the notation @UIApplicationMain or the UIApplicationMain() function.

The first step is remove the @UIApplicationMain annotation from AppDelegate.swift, create a new file main.swift . In this file we have to check if the app is launched by unit tests:


    let isRunningTests = NSClassFromString("XCTestCase") != nil
    

Now, we have to decide which app delegate class to load:


    return isRunningTests ? NSStringFromClass(TestAppDelegate.self) : NSStringFromClass(AppDelegate.self)

Instead of TestAppDelegate nil can be also return.


    return isRunningTests ? nil : NSStringFromClass(AppDelegate.self)

Final main.swift looks like this:


    import Foundation

    private func delegateClassName() -> String? {
        return NSClassFromString("XCTestCase") == nil ? NSStringFromClass(AppDelegate.self) : NSStringFromClass(AppDelegateStub.self)
    }

    UIApplicationMain(CommandLine.argc, CommandLine.unsafeArgv, nil, delegateClassName())


TestAppDelegate

We know that the TestAppDelegate is called just once and before the unit tests. It means that you have the possibility to run test logic in your TestAppDelegate once and before running the set of unit tests.


    import UIKit

    class TestAppDelegate: UIResponder, UIApplicationDelegate {
        var window: UIWindow?
    }

We don’t need to assign view controller to window here.

If you are using any ui test or snapshot framework which needs test to be hosted by an application with a key window then you need to use below test app delegate.


    import UIKit

    class TestAppDelegate: UIResponder, UIApplicationDelegate {
        var window: UIWindow?
        override init() {
            super.init()
            window = UIWindow(frame: UIScreen.main.bounds)
            window?.rootViewController = UIViewController()
            window?.makeKeyAndVisible()
        }
    }

After this change all tests got passed in improved time.

If you use core data in app delegate and want to initialize there is a good article to follow: Fake AppDelegate For Unit Testing In Swift

Conclusion

We’ve reduced the app launch step of testing to a bare minimum. It’s good to take this step early while setting up project because your real iOS app delegate will eventually grow.

Thanks for reading article.

This is a free third party commenting service we are using for you, which needs you to sign in to post a comment, but the good bit is you can stay anonymous while commenting.