Mocking UICollectionViewLayouts

July 10, 2014

At the heart of custom UICollectionViewLayouts are lots of calculations, and creating/debugging these by hand can be painful. It’s easier in the long run to write tests to help with this - but setting up the stack of objects to make the tests run can be a bit involved.

Here’s how I’m doing it - using XCTest and OCMock, although there’s no reason why this approach won’t work with other test/mock frameworks like Kiwi etc.

{% codeblock lang:objc) -(void)testCalculateSpokeRadiusReturnsCorrectValueForTwoItems {

UICollectionView *collectionView = [[UICollectionView alloc] initWithFrame:CGRectMake(0, 0, 500, 500) collectionViewLayout:self.customLayout];

id collectionViewMock = OCMPartialMock(collectionView);
[[[collectionViewMock stub] andReturnValue:@(1)] numberOfItemsInSection:0];

[collectionViewMock setCollectionViewLayout:self.customLayout];

[self.customLayout setItemSize:CGSizeMake(100, 100)];
[self.customLayout setSidePadding:10.0f];

XCTAssertEqual([self.customLayout calculateSpokeRadius], 190.0f, @"should be 190.0f");

} {% endcodeblock)

The process isn’t too gnarly. First, create a real, live UICollectionView instance and give it your custom layout (in this test, I’d previously instantiated the custom layout object in the test setup):

UICollectionView *collectionView = [[UICollectionView alloc] initWithFrame:CGRectMake(0, 0, 500, 500) collectionViewLayout:self.customLayout];

Then create a partial mock:

id collectionViewMock = OCMPartialMock(collectionView);

With this you, you can then stub out the numberOfItemsInSection: method and return the number of items you want to run the calculations for - by mocking out this method, you’ve got no dependencies on your datasources.

The advantage of using a partial mock is that you only need to stub out the methods that you want to control - you can use everything else as you would with the real, live object.

[[[collectionViewMock stub] andReturnValue:@(1)] numberOfItemsInSection:0];

Now link the custom layout and the collection view together:

[collectionViewMock setCollectionViewLayout:self.customLayout];

A few custom layout settings (these will obviously depend on how you’ve implemented your layout):

[self.customLayout setItemSize:CGSizeMake(100, 100)];`
[self.customLayout setSidePadding:10.0f];`

Finally, after all this, you can actually fire the test:

XCTAssertEqual([self.customLayout calculateSpokeRadius], 190.0f, @"should be 190.0f");

Here, I’ve created a helper method inside the custom layout to calculate the radius from the centre of the collection view for various sizes of layout. That’s often an easier approach to take - calculating layout attributes like item centre often involves some fiddly maths, so by breaking it up into chunks of helper methods you can test each bit piece-by-piece.

This tends to be easier in the long run than doing everything in one fell swoop, because you can spend a long time down the rabbit hole of figuring out where the layout is going wrong. With this test, I can throw various sizes of collection view at the layout, and check that things will still work out OK.