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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
// 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
@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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#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
1 2 3 4 5 6 7 |
- (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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
- (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
1 2 3 4 5 6 |
- (void)testOutlets { XCTAssertNotNil(self.vc.vkButton); XCTAssertNotNil(self.vc.fbButton); XCTAssertNotNil(self.vc.googleButton); XCTAssertNotNil(self.vc.agreementTextView); } |
Model logic
1 2 3 4 5 6 7 |
- (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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
- (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
1 2 3 4 |
- (void)testThatHelperHasCustomizeAppearanceMethod { XCTAssertTrue([self.helper respondsToSelector:@selector(customizeAppearance)]); } |
Testing bundle name
1 2 3 4 5 6 7 |
- (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.