bots

In WWDC 2013, Apple introduced Xcode 5 and iOS SDK 7 with a built-in framework for testing: XCTest.framework. Unfortunately Apple documentation lacks details for this framework. In this post I am going to present a simple way to test a UITableView using XCTest framework.

First, we need an Xcode project with a simple UITable. Open Xcode and create a new single-view application project. Open the storyboard file and drag a UITableView onto your view controller.

Storyboard

In the identity Inspector set “testTableView” as the Storyboard ID. Open up the ViewController.m file and place the code to poulate the dummy UITableView (do not forget to connect the IBOutlet to the TableView in the storyboard):

#import "ViewController.h"

@interface ViewController ()

@property (weak, nonatomic) IBOutlet UITableView *tableView;

@end

@implementation ViewController

#pragma mark - View loading methods
- (void)viewDidLoad
{
    [super viewDidLoad];
    self.title = @"XCTest Tutorial";
	// Do any additional setup after loading the view, typically from a nib.
}

#pragma mark - Memory warning methods
- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

#pragma mark - UITableView delegate methods
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return 15;
}

-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return 1;
}

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    NSString *reuseId = [NSString stringWithFormat:@"%ld/%ld",(long)indexPath.section,(long)indexPath.row];
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseId];
    if(!cell)
    {
        cell =[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:reuseId];
        cell.selectionStyle = UITableViewCellSelectionStyleGray;

    }

    cell.textLabel.text = [NSString stringWithFormat:@"Row %ld", (long)indexPath.row+1];

    return cell;
}

@end

As you can see the code is pretty self-explanatory. We just create a UITableView property with 15 rows and print the row number in each row (note that in your ViewController.h file you have to declare that the controller conforms to UITableViewDatasource and UITableViewDelegate protocols).

As you can see, I declared the UITableView property in my .m file interface section (which is the correct way to do it, because there is no need in our case to expose the UITableView to our .h file so it can be accessed by other classes). But we need to access it from our XCtest class. The easy solution would be to just move the declaration to the .h file, right? Yes, but it is not the most sophisticated solution. What are we going to do, is create a class extension which exposes the UITableView property for us. To do this, navigate to File>New>New file and create a Objective-C class extension file.

ClassExtension

Name the file “Private” and select ViewController as the class. Open up the newly created header file and just redeclare the UITableView there as shown in the following snippet:

#import "ViewController.h"

@interface ViewController ()

@property (weak, nonatomic) IBOutlet UITableView *tableView;

@end

Connect the IBOutlet in the tableView in the storyboard again. Now we are ready to write some tests for our UITableVIew. Open up the Tests.m file which is automatically created by Xcode 5 with every new project. Import the private header file we just created and declare a ViewController property:

#import <XCTest/XCTest.h>
#import "ViewController_Private.h"

@interface XCTestTutorialTests : XCTestCase

@property (nonatomic, strong) ViewController *vc;

@end

The first thing we want to test is that the view loads and has a UITableView as subview. In the setup method we put the code we want to run before each test -in our case we just want to load the viewcontroller- and in the teardown method we put the code we want to run after each test invocation:

- (void)setUp
{
    [super setUp];
    UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
    self.vc = [storyboard instantiateViewControllerWithIdentifier:@"testTableView"];
    [self.vc performSelectorOnMainThread:@selector(loadView) withObject:nil waitUntilDone:YES];

    // 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.
    self.vc = nil;
    [super tearDown];
}

#pragma mark - View loading tests
-(void)testThatViewLoads
{
    XCTAssertNotNil(self.vc.view, @"View not initiated properly");
}

- (void)testParentViewHasTableViewSubview
{
    NSArray *subviews = self.vc.view.subviews;
    XCTAssertTrue([subviews containsObject:self.vc.tableView], @"View does not have a table subview");
}

-(void)testThatTableViewLoads
{
    XCTAssertNotNil(self.vc.tableView, @"TableView not initiated");
}

The testThatViewLoads method checks that the viewcontroller’s view is not nil. Pretty simple test to check that a view is initiated. The testParentViewHasTableViewSubview tests that our view has UITableView subview and the testThatTableViewLoads tests that our tableView is not nil. Next we have some tests for the UITableView property:

#pragma mark - UITableView tests
- (void)testThatViewConformsToUITableViewDataSource
{
    XCTAssertTrue([self.vc conformsToProtocol:@protocol(UITableViewDataSource) ], @"View does not conform to UITableView datasource protocol");
}

- (void)testThatTableViewHasDataSource
{
    XCTAssertNotNil(self.vc.tableView.dataSource, @"Table datasource cannot be nil");
}

- (void)testThatViewConformsToUITableViewDelegate
{
     XCTAssertTrue([self.vc conformsToProtocol:@protocol(UITableViewDelegate) ], @"View does not conform to UITableView delegate protocol");
}

- (void)testTableViewIsConnectedToDelegate
{
    XCTAssertNotNil(self.vc.tableView.delegate, @"Table delegate cannot be nil");
}

- (void)testTableViewNumberOfRowsInSection
{
    NSInteger expectedRows = 15;
    XCTAssertTrue([self.vc tableView:self.vc.tableView numberOfRowsInSection:0]==expectedRows, @"Table has %ld rows but it should have %ld", (long)[self.vc tableView:self.vc.tableView numberOfRowsInSection:0], (long)expectedRows);
}

- (void)testTableViewHeightForRowAtIndexPath
{
    CGFloat expectedHeight = 44.0;
    CGFloat actualHeight = self.vc.tableView.rowHeight;
    XCTAssertEqual(expectedHeight, actualHeight, @"Cell should have %f height, but they have %f", expectedHeight, actualHeight);
}

- (void)testTableViewCellCreateCellsWithReuseIdentifier
{
    NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
    UITableViewCell *cell = [self.vc tableView:self.vc.tableView cellForRowAtIndexPath:indexPath];
    NSString *expectedReuseIdentifier = [NSString stringWithFormat:@"%ld/%ld",(long)indexPath.section,(long)indexPath.row];
    XCTAssertTrue([cell.reuseIdentifier isEqualToString:expectedReuseIdentifier], @"Table does not create reusable cells");
}

The code is pretty easy to understand. Note that in every assertion we have an expression and a format. The format acts like [NSString stringWithFormat:] by default, so there is no need to use stringWithFormat there.

Unit Tests are an essential part of a modern development flow and is nice to see that Apple acknowledges it and provided a framework for that. Unfortunately XCTest does not offer a way to create mock objects. To do that you have to rely on a third party framework like OCMock.  You can download the demo project used in this tutorial from Github.