I want to unit test a UICollectionView which is inside a UIViewController (e.g. I want to test that the UICollectionView has the number of cells that I am expecting it to have in my unit test)
My unit test is based on the following blog (on how to unit test a view controller): http://yetanotherdevelopersblog.blogspot.co.il/2012/03/how-to-test-storyboard-ios-view.html
In the unit test I am able to get a pointer to the UICollectionView in the UIViewController which I am testing. Here is the code in my test's setUp method:
storyboard = [UIStoryboard storyboardWithName:@"MainStoryboard" bundle:nil];
viewController = [self.storyboard instantiateViewControllerWithIdentifier:@"MainViewController"];
[viewController performSelectorOnMainThread:@selector(loadView) withObject:nil waitUntilDone:YES];
self.collectionView = viewController.collectionView
Unfortunately, there are no cells in this UICollectionView (i.e. self.collectionView.visibleCells.count
equals 0, hence I cannot access the cells and test that the data is what I am expecting it to be), although when debugging I can see that my application add those cells to the collection view.
What am I doing wrong? Any tips & tricks on how to unit test UICollectionView within a UIViewController?
Edit:
After Roshan's help I can now narrow down my problem. I am unit testing a UIViewController that has a UICollectionView. In my unit test I want to test that values of the cells inside CollectionView is what I am expecting it to be. But, collectionView:cellForItemAtIndexPath:
is not being invoked on my ViewController and hence my cells doesn't exist when this is invoked as a unit test. Any idea?
When implementing TTD on iOS, try not to rely in the system calling the delegate and data source methods.
You should be calling these methods directly from your unit tests. It's just a matter then of configuring the right environment for your tests.
For example, when I implement TDD for a UICollectionView, I create two separate classes specifically to implement the UICollectionViewDataSource and UICollectionViewDelegate protocols creating a separation of concerns and I can unit test these classes separately to the view controller itself, although I still need to initialize the view controller to setup the view hierarchy.
Here's an example, headers and other minor pieces of code omitted of course.
UICollectionViewDataSource example
@implementation CellContentDataSource
@synthesize someModelObjectReference = __someModelObjectReference;
#pragma mark - UICollectionViewDataSource Protocol implementation
- (NSInteger) collectionView:(UICollectionView *)collectionView
numberOfItemsInSection:(NSInteger)section
{
return __someModelObjectReference ?
[[__someModelObjectReference modelObjects] count] : 0;
}
- (UICollectionViewCell *) collectionView:(UICollectionView *)collectionView
cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
static NSString * const kCellReuseIdentifer = @"Cell";
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kCellReuseIdentifer forIndexPath:indexPath];
ModelObject *modelObject = [__someModelObjectReference modelObjects][[indexPath item]];
/* Various setter methods on your cell with the model object */
return cell;
}
@end
Unit Test Example
- (void) testUICollectionViewDataSource
{
UIStoryBoard *storyboard = [UIStoryboard storyboardWithName:@"MainStoryboard" bundle:nil];
MainViewController *viewController = [storyboard instantiateViewControllerWithIdentifier:@"MainViewController"];
[viewController view]; // Loads the view hierarchy
// Using OCMock to mock the returning of the model objects from the model object reference.
// The helper method referenced builds an array of test objects for the collection view to reference
id modelObjectMock = [OCMockObject mockForClass:[SomeModelObjectReference class]];
[[[modelObjectMock stub] andReturn:[self buildModelObjectsForTest]] modelObjects];
CellContentDataSource *dataSource = [CellContentDataSource new];
dataSource.someModelObjectReference = modelObjectMock;
viewController.collectionView.dataSource = dataSource;
// Here we call the data source method directly
UICollectionViewCell *cell = [dataSource collectionView:viewController.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
XCTAssertNotNil(cell, @"Cell should not be nil");
// Assert your cells contents here based on your test model objects
}
I don't know exactly where the problem is but Apple's documentation says this about loadView
:
You should never call this method directly. The view controller calls this method when the view property is requested but is currently nil. If you create your views manually, you must override this method and use it to create your views. If you use Interface Builder to create your views and initialize the view controller—that is, you initialize the view using the initWithNibName:bundle: method, set the nibName and nibBundle properties directly, or create both your views and view controller in Interface Builder—then you must not override this method.
So, instead of calling it directly, call [viewController view]
to force the view to be loaded.
As to your problem, check if viewController.collectionView
is 0 or not. It is possible that your outlet has not yet been set.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With