iOS Testing - Input fields validation testing (Part 5)

This is the part 5 in the series of articles on 'Unit Testing on iOS'. Below is the list of all articles and respective links to them.

  1. iOS Unit Tests - Testing models creation (Part 1)
  2. iOS Unit Testing - Testing View Controller (Part 2)
  3. iOS Unit Testing - Switching method implementations with OCMock (Part 3)
  4. iOS Testing - Testing asynchronous code (Part 4)
  5. iOS Testing - Input fields validation testing (Part 5)

In this part we will test the validation and testing of user input form. We will mock form field and input values. These values will then be fetched internally by form and validation will be performed. We will test the result of this validation with unit tests.

Following is the test code for header and implementation file

Header file


@interface JKUnitTestingFilterViewController : UIViewController

@property (nonatomic, strong) UIActivityIndicatorView* activityIndicatorView;
@property (nonatomic, strong) UITextField* nameField;
@property (nonatomic, strong) UITextField* passwordField;
@property (nonatomic, assign) BOOL invalidInputFlag;
@property (nonatomic, assign) BOOL actiAnimating;

- (void)toggleSpinner;
- (BOOL)validateFields;
- (void)showErrorMessage;

@end

Implementation file


#import 
#import "JKUnitTestingFilterViewController.h"

@interface JKUnitTestingFilterViewController ()

@end

@implementation JKUnitTestingFilterViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.activityIndicatorView = [[UIActivityIndicatorView alloc] init];
    [self.activityIndicatorView startAnimating];
    self.nameField = [[UITextField alloc] init];
    self.passwordField = [[UITextField alloc] init];
    
    self.view.backgroundColor = [UIColor redColor];
    self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] bk_initWithBarButtonSystemItem:UIBarButtonSystemItemDone handler:^(id sender) {
        [self dismissViewControllerAnimated:YES completion:NULL];
    }];
}

- (void)toggleSpinner {    
    if ([self.activityIndicatorView isAnimating]) {
        [self.activityIndicatorView stopAnimating];
    } else {
        [self.activityIndicatorView startAnimating];
    }
    self.actiAnimating = !self.actiAnimating;
}

- (BOOL)validateFields {
    self.invalidInputFlag = NO;
    BOOL valid = (self.nameField.text.length > 5 && self.passwordField.text.length > 5);
    if (!valid) {
        [self showErrorMessage];
    }
    return valid;
}

- (void)showErrorMessage {
    self.invalidInputFlag = YES;
}

@end

This viewcontroller has two input fields. Username and password. We want both this field to be at least 6 characters long. When user presses submit button, this form will call the validator method 'validateFields'. This method will check if both username and password adhere to the policy on number of characters in username and email. If all the fields are valid, invalidInputFlag is set to true. If at least one field is invalid, this same flag is set to false and hypothetical error message will be shown.

Now, just a starting point here is the list of things we want to test in the context of this view controller,

  • Test 1 - When both username and password fields are at least 6 characters long, validateFields method should return true. At the same time flag invalidInputFlag will be set to false

  • Test 2 - When either username or password field is not at least 6 characters long, validateFields method should return false. At the same time flag invalidInputFlag will be set to true

  • Test 3 - Calling method toggleSpinner should toggle the spinner between animation begin/end states

  • Test 4 - When method validateFields is called with invalid inputs, we need to verify that method showErrorMessage is called to notify user of unacceptable input values

Below are the unit tests I wrote in order to verify the expected behavior


#import <Specta/Specta.h>
#import <OCMock/OCMock.h>
#import <Expecta/Expecta.h>
#import <XCTest/XCTest.h>
#import "JKUnitTestingFilterViewController.h"

@interface JKUnitTestsViewControllerTests : XCTestCase

@property (nonatomic, strong) id mockFilterViewController;
@property (nonatomic, strong) JKUnitTestingFilterViewController* filterVC;

@end

@implementation JKUnitTestsViewControllerTests

// Setting up the variable for testing.
- (void)setUp {
    [super setUp];
// Create actual view controller.
    self.filterVC = [[JKUnitTestingFilterViewController alloc] init];
// Create a mock for view controller.
    self.mockFilterViewController = [OCMockObject partialMockForObject:self.filterVC];
}

// Testing the scenario Test 1 with valid values for both username and password
- (void)testValidValues {
    id nameField = [OCMockObject mockForClass:[UITextField class]];
    [[[nameField stub] andReturn:@"asdadasda"] text];
    id passwordField = [OCMockObject mockForClass:[UITextField class]];
    [[[passwordField stub] andReturn:@"asdasdasd as das"] text];
    OCMStub([self.mockFilterViewController nameField]).andReturn(nameField);
    OCMStub([self.mockFilterViewController passwordField]).andReturn(passwordField);
    XCTAssertEqual([self.mockFilterViewController validateFields], YES);
    XCTAssertEqual([self.mockFilterViewController invalidInputFlag], NO);
}

// Testing the scenario Test 2 with invalid values for both username and password

- (void)testInvalidValues {
    id nameField = [OCMockObject mockForClass:[UITextField class]];
    [[[nameField stub] andReturn:@""] text];
    id passwordField = [OCMockObject mockForClass:[UITextField class]];
    [[[passwordField stub] andReturn:@""] text];
    OCMStub([self.mockFilterViewController nameField]).andReturn(nameField);
    OCMStub([self.mockFilterViewController passwordField]).andReturn(passwordField);
    XCTAssertEqual([self.mockFilterViewController validateFields], NO);
    XCTAssertEqual([self.mockFilterViewController invalidInputFlag], YES);
}

// Testing the scenario Test 3 to verify the activity spinner toggle action. 

- (void)testSpinnerLoad {
    XCTAssertEqual([self.mockFilterViewController actiAnimating], NO);
    [self.mockFilterViewController toggleSpinner];
    XCTAssertEqual([self.mockFilterViewController actiAnimating], YES);
}

// Testing the scenario Test 4 to verify the expected method in indeed called. We setup the expectation with method name first. Call another method with desired result of calling the expected method. And then verify if expected method was called. 

- (void)testDisplayErrorMessage {
    id nameField = [OCMockObject mockForClass:[UITextField class]];
    [[[nameField stub] andReturn:@""] text];
    id passwordField = [OCMockObject mockForClass:[UITextField class]];
    [[[passwordField stub] andReturn:@""] text];
    OCMStub([self.mockFilterViewController nameField]).andReturn(nameField);
    OCMStub([self.mockFilterViewController passwordField]).andReturn(passwordField);
// Setup the expectation.
    [[self.mockFilterViewController expect] showErrorMessage];
// Call method 'validateFields' to verify the input values.
    [self.mockFilterViewController validateFields];
// Method 'showErrorMessage' should be called since we already passed invalid input values. 
    [self.mockFilterViewController verify];
}
@end

The full source code is available on GitHub with code samples included for all 5 articles in the series of Unit Testing on iOS

Go to previous article