Cuckoo for Cocoa Dev

A blog for all those Cuckoo for Cocoa Development

Behavioral Driven Development (BDD) Using Specta Part 2 - Advanced Techniques

AppDelegate… It’s one of those classes that can typically become a dumping ground for a bunch of code that is global (even though it probably shouldn’t be, see God Object). Well, that’s an entire discussion that could merit an entire blog post itself, but for now, let’s talk about how to avoid the execution of code in the AppDelegate in tests by using a simple trick in main.m.

The AppDelegate class is usually the home of such SDK initializations such as Crashlytics, Urban Airship, CocoaLumberjack, etc. Unfortunately if we have a test target that runs specs/tests, when the tests first load, main.m has this block of code that runs:

main.m
1
2
3
4
5
6
7
8
@import UIKit;
#import "AppDelegate.h"

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

As we can see here, we end up using the AppDelegate class to handle the callbacks from the UIApplication. This is true for not only the runnable application itself, but is also used when a test suite is run. To give a better demonstration, let’s give the AppDelegate some work to do:

AppDelegate.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#import "AppDelegate.h"

@implementation AppDelegate

#pragma mark - <UIApplicationDelegate>

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    [self doSomeWork];
    [self doSomeExtraWork];

    return YES;
}

#pragma mark - Private Methods

- (void)doSomeWork
{
    NSLog(@"Doing some work...");
    sleep(5);
    NSLog(@"Finished doing some work...took 5 seconds!!!");
}

- (void)doSomeExtraWork
{
    NSLog(@"Doing some xtra work...");
    sleep(7);
    NSLog(@"Finished doing some xtra work...took 7 seconds!!!");
}

@end

This work is going to take a while, let’s see what happens when we run the tests:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Test Suite 'Specs.xctest' started
CuckooForSpecta[77] Doing some work...
CuckooForSpecta[77] Finished doing some work...took 5 seconds!!!
CuckooForSpecta[77] Doing some xtra work...
CuckooForSpecta[77] Finished doing some xtra work...took 7 seconds!!!

...  a bunch of messages about how tests haven’t run because it’s taking too long  ...

...  a bunch of tests run from the previous blog post  ...

Test Suite 'Specs.xctest' passed.
   Executed 11 tests, with 0 failures (0 unexpected) in 0.022 (0.026) seconds
Test Suite 'All tests' passed.
   Executed 11 tests, with 0 failures (0 unexpected) in 0.022 (0.027) seconds

Having to deal with an extra 12 seconds of AppDelegate work is going to get old really quick, not to mention it can even run code that could negatively affect your tests in general (hint: CocoaLumberjack and test frameworks don’t play well together). Wouldn’t it be nice to prevent this code from running period? With this neat little trick, you can =)

Let’s create a new test AppDelegate class (you only need to add this to your test target):

TestingAppDelegate.h/.m
1
2
3
4
5
6
7
8
9
10
@import UIKit;

@interface TestingAppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow *window;
@end

#import "TestingAppDelegate.h"

@implementation TestingAppDelegate
@end

Note* The only reason a window property was added to this test app delegate is to prevent a warning log message when specs run.

Now let’s dig into main.m and do some trickery:

main.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@import UIKit;
#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));
    }
}

While tricky, it’s pretty simple. We set the appDelegateClass to be the TestingAppDelegate we just created. If it doesn’t exist the regular AppDelegate is used. As you have probably guessed, since we have the TestingAppDelegate attached to the Specs target, the only time it will be used is when we are running the specs. Let’s run some specs now…

1
2
3
4
5
6
7
8
Test Suite 'Specs.xctest' started

...  a bunch of tests run from the previous blog post  ...

Test Suite 'Specs.xctest' passed.
   Executed 11 tests, with 0 failures (0 unexpected) in 0.019 (0.022) seconds
Test Suite 'All tests' passed.
   Executed 11 tests, with 0 failures (0 unexpected) in 0.019 (0.023) seconds

Well there you have it! A nice way to speed up tests and avoid unnecessary app setup that is usually placed in the app delegate (and avoid initialization of pesky singletons that make testing just that much more difficult). With all the time you’ll be saving with this change you will have so much more time to do activities!

Comments