Getting started with Unit Testing in Xcode 7

Unit Testing, or XCTesting as it’s called since Xcode 5, is a process that lets us test several aspects of our apps. For example, we can see if methods have return values we expect, and if not, flag this up to us. Since Xcode 7 and iOS 9 we can even test user interface events too.

Testing has come a long way in Xcode, but it’s also long been a mystery to me. I decided to explore it and make some notes so I wouldn’t forget.

Turns out that Unit Testing isn’t actually that difficult a concept to comprehend, but it’s usually presented in a way that only those with an IQ of 1000 or greater would understand it. Apple’s new document called Testing with Xcode has certainly helped me understand the basics, which I’ll discuss in this article.

Where to begin

Any project that was started with Xcode 5 and higher already has test targets setup by default. Think of them as ordinary targets of your app. The only difference is that they show up in a different part of Xcode: instead of the Project Navigator (left hand pane), they’ll show up in the Testing Navigator.

Screen Shot 2015-11-09 at 21.45.05

By default we’ll see two groups with a white folder icon: Tests and UITests, prefaced by our app’s title (in my case that’s Unit Testing Demo). Apple calls these white groups Test Bundles.

In these white folders we can see several blue icons with seemingly the same name with a capital T, and in those we see several blue icons with a lower case T. Apple calls these blue groups Test Classes. Each class can have several Test Methods that we can execute.

No wonder this is a tad confusing!

Let’s forget about UI Testing for now and focus on Unit Testing instead: that’s the top white folder in the screenshot. Those two Test Methods correspond directly to the code it brings up. Select the Test Class and we can see something like this:

#import <XCTest/XCTest.h>

@interface Unit_Testing_DemoTests : XCTestCase

@end

@implementation Unit_Testing_DemoTests

- (void)setUp {
    [super setUp];
    // Put setup code here. This method is called before the invocation of each test method in the class.
}

- (void)tearDown {
    // Put teardown code here. This method is called after the invocation of each test method in the class.
    [super tearDown];
}

- (void)testExample {
    // This is an example of a functional test case.
    // Use XCTAssert and related functions to verify your tests produce the correct results.
}

- (void)testPerformanceExample {
    // This is an example of a performance test case.
    [self measureBlock:^{
        // Put the code you want to measure the time of here.
    }];
}

This looks like half-familiar Objective-C code. Think of this as an implementation file: Test Classes only have a single file. We can declare properties and methods as we know and love, and we can also use @import statements.

Xcode provides two sample methods here: testExample and testPerformanceExample. Those methods correspond to the blue icons in the Test Navigator. We can write our own methods and they’ll appear in that list too: they need to be void methods and start with the word “test” (other methods are ignored). Let’s try it out:

- (void)testSomethingElse {
    
}

Hit CMD+B and that method will show up in the sidebar too.

Screen Shot 2015-11-09 at 22.05.06

If you hover over one of our methods, you’ll notice a little playback icon appear. Click it to run this single test. The same playback button will also appear a level higher on the Test Class, and also on the Test Bundle. Clicking one of those runs all tests in the group.

In addition to these two default methods, there’s also a setup and a tearDown method. Those are equivalent to something like our old friends viewDidLoad / viewDidUnload or applicationDidFinishLaunching / applicationWillTerminate, but of course they’re only working inside our Test Class. We can code anything that needs to be setup before any test begins and ends.

Running Tests

When we execute that little playback icon, Xcode will build and run the app, either on an attached device or the simulator. Make sure to select the correct device first.

Right now all our tests will be successful, as indicated by the little green tick marks both in the code gutter as well as the Test Navigator. That’s because we’re not really testing anything yet. Let’s do that next.

XCTAssert Functions

XCTesting has its own keywords that can test the outcome of whatever we’re testing. Usually this boils down to something like “is this value NIL” or “is this value greater than that”, much like the sort of stuff we would do in if/then queries.

For example, let’s setup an integer and then compare it to another one. If it’s the same value, the test will pass; if not, the test will fail. Sounds simple enough.

- (void)testSomethingElse {
    
    int first = 12;
    int second = 13;
    
    XCTAssertEqual(first, second);
}

Run the test and see it fail miserably – as indicated by the daunting red diamond symbol in several places. Change the values to match and the test will pass.

Screen Shot 2015-11-09 at 22.21.23

In the above test we’ve used the XCTAssertEqual keyword. It compares the two values in brackets and asks “is the first value equal to the second”. Code completion will show you several other self-explanatory keywords as soon as you start typing.

Play around with some of the other XCTAssert functions to get a feel for running these tests.

Connecting with our AppDelegate

We can test methods in any of our classes, including the AppDelegate. For any of these tests to be useful, we must connect a Test Class to our app from the outside. For example, if there’s a method in our AppDelegate that we would like to test, then the first step is to grab a reference to our AppDelegate.

Anything we test needs to be public (i.e. declared in the header file), no matter which class we want to test. Private properties or methods cannot be tested.

Let’s imagine we had such a method, and it returns an NSString. We can write a test to check if the correct value is returned:

#import "AppDelegate.h"

// ...

- (void)testAppDelegateMethod {
    
    // grab a reference to our AppDelegate
    AppDelegate *myAppDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
    NSString *string = [myAppDelegate sayHello];
    
    XCTAssertTrue([string isEqualToString:@"Hello!"]);
    
}

We can grab a reference to our own AppDelegate using the UIApplication class. Next we call the method in question and grab the string value it returns. And finally we use the XCTAssertTrue function to see if the outcome of our isEqualToString method is true or false. If the method returns “Hello!”, the test will pass.

Connecting to active View Controllers

To gain access to public methods written in View Controller Classes, we must likewise grab references to those active classes. Instantiating them again from the storyboard is not going to work, we must instead get them via the AppDelegate.

Here’s an example of a single view application, whose public method also returns a string:

#import "ViewController.h"

// ...

- (void)testViewControllerMethod {
    
    // grab a reference to our AppDelegate 
    AppDelegate *myAppDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
    
    // and our view controller
    ViewController *vc = (ViewController *)myAppDelegate.window.rootViewController;
    NSString *string = [vc sayGoodbbye];
    
    XCTAssertTrue([string isEqualToString:@"Goodbye!"]);
}

You get the picture. It can become slightly tricky if you want to grab a reference to a view controller that’s on the stack of a navigation controller, which is also embedded in a tab bar or split view controller – but if you’re looking into Unit Testing, then I trust you’ve come across such hurdles before and know how to handle them.

Setup and Tear Down methods

The Test Class is just an ordinary implementation file, and as such it can have properties too. Say you had several tests and you want to keep that extracted AppDelegate handy for all your tests, you could initialise such a property in the setup method:

@property (nonatomic, strong) AppDelegate *appDelegate;

// ...

- (void)setUp {
    [super setUp];
    
    self.appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
}

Now each separate test you write can access the self.appDelegate property. If you’re a belt-and-braces kind of guy you can even set it to nil in the tearDown method.

This should get you started with XCTesting. For more information, check out Apple’s XCTesting documentation: https://developer.apple.com/library/content/documentation/DeveloperTools/Conceptual/testing_with_xcode/chapters/01-introduction.html#//apple_ref/doc/uid/TP40014132-CH1-SW1





Leave a Reply