Search code examples
iosunit-testingcore-dataocunitocmock

How to unit test NSFetchedResultsControllerDelegate?


I am trying to write unit tests for a view controller that implements the NSFetchedResultsControllerDelegate protocol. The first test of the implementation (after some other tests of this view controller) is to verify that a table row is inserted into the table view when a new object is inserted.

My first implementation of the test was:

- (void) setUp {
    [super setUp];

    sut = [[JODataTableViewController alloc] init];
    fetchedResultsCtrlrMock = [OCMockObject niceMockForClass:[NSFetchedResultsController class]];
    NSError *__autoreleasing *err = (NSError *__autoreleasing *) [OCMArg anyPointer];
    [[[fetchedResultsCtrlrMock expect] andReturnValue:OCMOCK_VALUE((BOOL){YES})] performFetch:err];
    [sut setValue:fetchedResultsCtrlrMock forKey:@"fetchedResultsController"];
    [sut view]; // This invokes viewDidLoad.
}

- (void) tearDown {
    sut = nil;

    [super tearDown];
}

- (void) testObjectInsertedInResultsAddsARowToTheTable {
    NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
    id tableViewMock = [OCMockObject mockForClass:[UITableView class]];
    sut.tableView = tableViewMock;
    [[tableViewMock expect] insertRowsAtIndexPaths:@[indexPath]
                                  withRowAnimation:UITableViewRowAnimationLeft];

    [sut controller:nil didChangeObject:nil
        atIndexPath:nil
      forChangeType:NSFetchedResultsChangeInsert
       newIndexPath:indexPath];

    [tableViewMock verify];
}

When it tried to implement the functionality in the view controller to move to the green status (TDD), I wrote the following code:

- (void) controllerWillChangeContent:(NSFetchedResultsController *)controller {
    [self.tableView beginUpdates];
}


- (void) controller:(NSFetchedResultsController *)controller
    didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath
      forChangeType:(NSFetchedResultsChangeType)type
       newIndexPath:(NSIndexPath *)newIndexPath {
    UITableViewCell *cell;

    switch (type) {
        case NSFetchedResultsChangeInsert:
            [self.tableView insertRowsAtIndexPaths:@[newIndexPath]
                                  withRowAnimation:UITableViewRowAnimationLeft];
            break;

    }
}


- (void) controllerDidChangeContent:(NSFetchedResultsController *)controller {
    [self.tableView endUpdates];
}

However, I couldn't get it to pass and the error is:

Test Case '-[JODataTableViewControllerTests testObjectInsertedInResultsAddsARowToTheTable]' started.
Unknown.m:0: error: -[JODataTableViewControllerTests testObjectInsertedInResultsAddsARowToTheTable] : OCMockObject[UITableView]: unexpected method invoked: isKindOfClass:<??> 
Test Case '-[JODataTableViewControllerTests testObjectInsertedInResultsAddsARowToTheTable]' failed (0.001 seconds).

I tried to add one or more times the following line to the preparation part of the test with the same results.

[[[tableViewMock expect] andReturnValue:OCMOCK_VALUE((BOOL){YES})] isKindOfClass:[OCMArg any]];

As you can see, I am currently using OCUnit and OCMock. I would consider other tools only if it is impossible to create these kind of tests with this toolset, and in that case I would appreciate to an explanation of their limitations, should they exist.

As far as I understand, the mock is unable to "lie" about the nature of its class even when told to do so. Also, the error doesn't provide information about the class UITableView is looking for. I know that it isn't a good practice for testing to use -isKindOfClass:, but it isn't my code.

Thank you for your help.


Solution

  • One way to work around mocking objects that have concealed inner behaviors is to use a partial mock. In your case:

    id tableViewMock = [OCMock partialMockForObject:[[UITableView alloc] init]];
    

    When I do this I usually change the name slightly and would call it tableViewPartial