Question

I'm getting started with TDD and got stuck testing a simple UITableViewController (using storyboards).

The tableView should have one row for every element in my model NSArray:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return [self.sortedStories count];
}

My test for this is:

- (void)testTwoStoriesShouldLeadToTwoRowsInSectionZero
{
    _sut.sortedStories = [self arrayWithTwoStories];
    [_sut.tableView reloadData];

   XCTAssertEqual([_sut tableView:_sut.tableView numberOfRowsInSection:0], 2, @"The number of rows should match the number of stories");
}

And I'm initializing my _sut in my test class from my storyboard like this:

- (void)setUp
{
    [super setUp];
    UIStoryboard *storyboard =
    [UIStoryboard storyboardWithName:@"Main_iPhone"
                              bundle:nil];

    _sut = [storyboard instantiateViewControllerWithIdentifier:
            @"MyTableViewController"];
}

This works perfectly fine if there is no setup of the model in my production code. But after I added this default setup with seven stories in my UITableViewController's viewDidLoad method:

- (void)viewDidLoad
{
    [super viewDidLoad];
    self.sortedStories = [self sevenDefaultStories];
}

the test suddenly fails, stating that 'seven isn't equal to two'. The tableView seems not to reload the data, though I have the [_sut.tableView reloadData] in my test after changing the model.

What am I doing wrong?

Was it helpful?

Solution

I found it:

Though the tableViewController was instantiated via storyboard etc., the view had not been loaded yet!

So viewDidLoad also had not been called yet, when I 'changed' my model. But then, when I called [_sut.tableView reloadData] (which should make sure, the tableView would get synced with my model), this caused exactly the opposite: accessing the tableView made it necessary that the view got loaded, so NOW finally viewDidLoad was called on my tableViewController! And so my model got changed again, overwriting my nice test model with default values.

The solution was, to add [_sut view]; to the top of my test case. This makes sure that the view is already loaded when my test begins.

And, in my case, calling [_sut.tableView reloadData] wasn't necessary at all, since I'm only testing the dataSource method directly, not the actual tableView. N.B.:[_sut view] still needs to be called, because the dataSource method in my XCTAssertEqual() statement also triggers the view to be loaded, if that has not happened yet.

So the working test case looks like this:

- (void)testTwoStoriesShouldLeadToTwoRowsInSectionZero
{
    [_sut view];   
    _sut.sortedStories = [self arrayWithTwoStories];

    XCTAssertEqual([_sut tableView:_sut.tableView numberOfRowsInSection:0], 2, @"The number of rows should match the number of stories");
}

OTHER TIPS

During test setup (my preferred solution):

[UIApplication sharedApplication].keyWindow.rootViewController = _sut; // Suck it UIKit

You may alternatively consider:

[[UIApplication sharedApplication].keyWindow addSubview:_sut.view]; // Hax

Note that UITableView may not render any cells unless it has been added to the view hierarchy. Similarly, UIViewController lifecycle methods may not be called unless it is added to the view controller hierarchy. I currently use "my preferred solution" above because it accomplishes both.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top