In this article I will post common iOS Unit testing examples for testing of:
- Asynchronous loading of data using REST API
- Loading of a UIView
- Method call after UITableViewCell is clicked
- Static UITableView sections have correct number of rows
- Loading of a UIView from UIStoryboard
- IBOutlet connection
- Model logic
- UITableViewCell subclass
- IBAction / Method is implemented
- Testing bundle name
Asynchronous loading of data using REST API
// Testing REST API GET /api/cities/ that returns an array of cities with id and name
- (void)testCitiesAPI
{
// We create XCTestExpectation object to test asynchronous loading
XCTestExpectation *testExpectation = [self expectationWithDescription:@"testCitiesAPI"];
NSString *URLString = [NSString stringWithFormat:@"/api/cities/"];
[[RSHTTPClient sharedClient] GET:URLString parameters:nil progress:nil success:^(NSURLSessionTask *task, id responseObject) {
NSArray *jsonArray = (NSArray *)responseObject;
// Checking that jsonArray is not empty
XCTAssertNotNil(jsonArray);
for (NSDictionary *dic in jsonArray) {
RSCity *city = [RSCity objectWithProperties:dic];
// Checking consistency of a model object created from JSON dic
XCTAssertNotNil(city.name);
XCTAssertNotEqual(city.sid, 0);
}
[testExpectation fulfill];
} failure:^(NSURLSessionTask *task, NSError *error) {
NSLog(@"Error: %@", error);
XCTFail(@"Error in GET /api/cities/");
}];
// Waiting for 10 seconds untim fulfill is called when task is finished
[self waitForExpectationsWithTimeout:10 handler:^(NSError *error) {
}];
}
Here [RSHTTPClient sharedClient] is an instance of AFHTTPSessionManager subclass. But it doesn’t matter, the main is the principle.
Loading of a UIView
@interface RSBaseViewControllerTests : XCTestCase
@property (nonatomic, strong) RSBaseViewController *vc;
@end
@implementation RSBaseViewControllerTests
- (void)setUp {
[super setUp];
self.vc = [[RSBaseViewController alloc] init];
[self.vc view];
}
- (void)tearDown {
self.vc = nil;
[super tearDown];
}
- (void)testThatViewLoads {
XCTAssertNotNil(self.vc.view);
}
@end
Method call after UITableViewCell is clicked
#import "OCMock.h"
# Mock objects for Objective-C
pod 'OCMock', '~> 3.4'
- (void)testThatHomekitRestoreCalled
{
id mock = [OCMockObject partialMockForObject:self.vc];
[[mock expect] restoreHomeKitClicked];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:CCSettingsSectionRestoreHomeKit];
[self.vc tableView:self.vc.tableView didSelectRowAtIndexPath:indexPath];
[mock verify];
}
Here we verify that method restoreHomeKitClicked is called when user selects a specific row in UITableView.
Static UITableView sections have correct number of rows
- (void)testThatSectionsHaveCorrentNumberOfRows
{
XCTAssertEqual([self.vc tableView:self.vc.tableView numberOfRowsInSection:CCSettingsSectionUpdate], 1);
XCTAssertEqual([self.vc tableView:self.vc.tableView numberOfRowsInSection:CCSettingsSectionGeneral], 2);
XCTAssertEqual([self.vc tableView:self.vc.tableView numberOfRowsInSection:CCSettingsSectionAdditional], 3);
XCTAssertEqual([self.vc tableView:self.vc.tableView numberOfRowsInSection:CCSettingsSectionRestoreHomeKit], 1);
}
Loading of a UIView from UIStoryboard
- (void)setUp
{
[super setUp];
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
self.vc = [storyboard instantiateViewControllerWithIdentifier:@"RECameraListViewController"];
[self.vc performSelectorOnMainThread:@selector(loadView) withObject:nil waitUntilDone:YES];
[self.vc view];
}
- (void)tearDown
{
self.vc = nil;
[super tearDown];
}
#pragma mark - View loading tests
- (void)testThatViewLoads
{
XCTAssertNotNil(self.vc.view, @"View not initiated properly");
}
IBOutlet connection
- (void)testOutlets {
XCTAssertNotNil(self.vc.vkButton);
XCTAssertNotNil(self.vc.fbButton);
XCTAssertNotNil(self.vc.googleButton);
XCTAssertNotNil(self.vc.agreementTextView);
}
Model logic
- (void)testBalanceArrayCount
{
CSAccount *account = [CSAccount sharedAccount];
NSArray *balanceArray = [account balanceArrayForNumberOfMonths:12];
XCTAssertEqual([balanceArray count], 13);
}
Here we test that an array, that is formed inside our model has correct number of items. Model logic testing obviously can be different depending of logic itself.
UITableViewCell subclass
- (void)testThatServerCellHasCorrectOutlets
{
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
REServerCell *cell = (REServerCell *)[self.vc tableView:self.vc.tableView cellForRowAtIndexPath:indexPath];
XCTAssertNotNil(cell.cloudServerLabel, @"Should connect cloudServerLabel IBOutlet.");
XCTAssertNotNil(cell.controlCenterServerLabel, @"Should connect controlCenterServerLabel IBOutlet.");
}
- (void)testThatServerCellHasCorrectContents
{
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
REServerCell *cell = (REServerCell *)[self.vc tableView:self.vc.tableView cellForRowAtIndexPath:indexPath];
REServerItem *item = self.vc.tableData[0];
NSString *cloudServerURL = cell.cloudServerLabel.text;
NSString *controlCenterServerURL = cell.controlCenterServerLabel.text;
XCTAssertTrue([cloudServerURL isEqualToString:item.cloudServer]);
XCTAssertTrue([controlCenterServerURL isEqualToString:item.controlCenterServer]);
}
IBAction / Method is implemented
- (void)testThatHelperHasCustomizeAppearanceMethod
{
XCTAssertTrue([self.helper respondsToSelector:@selector(customizeAppearance)]);
}
Testing bundle name
- (void)testThatBundleNameIsCorrect
{
NSString *correctBundleName = @"YourBundleName";
NSDictionary *infoDic = [[NSBundle mainBundle] infoDictionary];
NSString *bundleName = infoDic[@"CFBundleName"];
XCTAssertTrue([bundleName isEqualToString:correctBundleName], @"Strings are not equal %@ %@", correctBundleName, bundleName);
}
The last is a rare case, but helps to prevent someone changing a bundle name of project accidentally.