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.
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