Xcode 5: Test UITableView with XCTest framework

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):

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:

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:

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:

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:

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.

11 thoughts on “Xcode 5: Test UITableView with XCTest framework

  1. Nscocoalab

    Great Tutorial, there’s not many articles about how to affront the unit testing and implement ttd in iOS with XCTests.

    Is really useful, in other hand do you read about TDD? And if is affirmative how you can confront the ttd development with the iOS creating cycle or how you work with unit testing (you first develop the view controller and later think in the tests or not???)

    Thanks again for your tuto.

  2. Nscocoalab

    about the private class extension, do you reference this from the real view-controller I mean if later I change something of the private properties in the real .m class , because the test imports the private file, do you think that maybe is a good idea to import the private from the .m view controller file too? With this there’s only one Iboutlet connected …

    1. Nikos M. Post author

      @Nscocoalab to reference a property from an other class the property have to be exposed to an interface file. We use the private class extension to expose the properties and reference them to our test classes. There is no need to expose them in the .h file of the class because they don’t need to be referenced from other classes in our app.

  3. Suresh D

    Hi, thank you so much for detail explanation. I have one question.
    Can we check table view didSelectRowAtIndexPath action( i.e click action for table view)?
    For button, we follow below code. is there any code to determine UITableView click action.
    -(void)testNewUserButtonIBAction {
    NSArray *actions = [mLoginViewController.mNewUserBtn actionsForTarget:mLoginViewController forControlEvent:UIControlEventTouchUpInside];
    XCTAssertTrue([actions containsObject:@"newUserAction:"], @”newUserAction: Event Fired”);
    }

    Thanks in advance.

    1. Nikos M. Post author

      @SureshD: You can get the touch point for the cell and from there you can get the indexPath. Knowing the indexpath will make it easy to determine the action which cell is selected and if it corresponds to the correct action.

      CGPoint center= sender.center;
      CGPoint rootViewPoint = [sender.superview convertPoint:center toView:self.tableView];
      NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:rootViewPoint];

      1. Suresh D

        @Nikon, how can I get the sender instance in test case method?
        When we run test cases for an app, we don’t get any user interaction to the application. In that case, how can a test case knows about sender event?
        When the app is in foreground( building mode ), your code will work give selected index.

  4. Suresh D

    Hi Nikon,

    I have one more question for you. below test case method is failing, why?
    -(void)testPrevButtonIBAction {
    LandingViewController *mLandingViewController = [[LandingViewController alloc] initWithNibName:@”LandingViewController” bundle:nil];
    NSArray *actions = [mLandingViewController.mPrevBtn actionsForTarget:mLandingViewController forControlEvent:UIControlEventTouchUpInside];
    XCTAssertTrue([actions containsObject:@"prevAction:"], @”prevAction: Event Fired”);
    }
    When I debug the object, nib controls are not initialized. Why controls are not initialized in this case and how can I test a view-controller button action(above kind of scenarios).
    Thanks in advance.

      1. Suresh D

        @Nikon, Thank you. The provide link might give me solution. I didn’t try it as I don’t have time to work on it.
        But, on last day, I have worked a lot to test a View Controller and its properties. Please find the below code snippet and advice me if there is any wrong.
        -(void)testNextButtonIBAction {
        AppDelegate* delegate = ((AppDelegate*) [[UIApplication sharedApplication] delegate]);
        LogMealsViewController *mLogMealsViewController = [[LogMealsViewController alloc] initWithNibName:@”LogMealsViewController” bundle:nil];
        UINavigationController *mNavigationController = [[UINavigationController alloc] initWithRootViewController:mLogMealsViewController];
        delegate.window.rootViewController = mNavigationController;
        [delegate.window makeKeyAndVisible];

        NSArray *actions = [mLogMealsViewController.mNext2Btn actionsForTarget:mLogMealsViewController forControlEvent:UIControlEventTouchUpInside];
        XCTAssertTrue([actions containsObject:@"nextAction:"], @”nextAction: Event Fired”);
        }

        The above method( approach ) is working fine for all the View-controllers. Here what I am doing is, In every test case method, I am creating corresponding view-controller object and assigning it to window’s rootViewController, then I am writing test scripts for my buttons. is this the right way? Please give your valuable comments on this approach.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">