iOS Unit Testing - Switching method implementations with OCMock (Part 3)

This is the part 3 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)

While writing tests for API class I realized that I wanted to test a function with mocked

  1. Return values
  2. Parameters
  3. Implementation
  4. Alternate testing implementation

I already have an implementation method in the class as follows :


@implementation JKUnitTestsDemoOperations

- (NSInteger)addNumbers:(NSInteger)num1 andNum2:(NSInteger)num2 {
    return num1 + num2;
}

+ (NSInteger)multiplyNumbers:(NSInteger)num1 andNum2:(NSInteger)num2 {
    return num1 * num2;
}

- (void)printOperation {
    NSLog(@"We are having some important print operation right now");
}

- (NSString*)myName {
    return @"Jayesh Kawli";
}

- (NSString*)invocationAlternateMethod:(NSString*)brideName {
    return @"Unit Tests are not important";
}

@end

These method swappings are important if you are fetching a network data and during testing you want to stub it to load similar data from local resource

In the following paragraph we will see how you can switch return values from these methods and swap an implementation with other methods.


// Standard iOS testing frameworks.
#import <Specta/Specta.h>
#import <OCMock.h>
#import "JKUnitTestsDemoOperations.h"
#import "JKUnitTestsAlternativeOperation.h"

SpecBegin(OperationTests)

__block id operation;
__block JKUnitTestsAlternativeOperation* alternate;

describe(@"Verifying the demo operations class functionality", ^{
    beforeAll(^{
// Mocks for our classes.
        operation = OCMClassMock([JKUnitTestsDemoOperations class]);
        alternate = OCMClassMock([JKUnitTestsAlternativeOperation class]);
        
// We are stubbing method addNumbers so that when we pass input values of 10 and 30, it will always return the value of 40 when method is called by the mock.
        OCMStub([operation addNumbers:10 andNum2:30]).andReturn(40);
// This is how you stub a class/static method.
        OCMStub(ClassMethod([operation multiplyNumbers:10 andNum2:300])).andReturn(3000);
// This is how you stub instance method.
        OCMStub([alternate alwaysReturns1000]).andReturn(@"1000");
// This is method swapping. When a mock 'operation' calls method 'myName', the following code will actually cause to call a method 'alwaysReturns1000' instead.
        OCMStub([operation myName]).andCall(alternate, @selector(alwaysReturns1000));

// The following code will change the implementation of method 'invocationAlternateMethod'. inside 'andDo' block, method can grab passed arguments, do calculation and return the alternate value.
        [[[operation stub] andDo:^(NSInvocation* invocation) {
            NSString* firstArgument = nil;
            [invocation getArgument:&firstArgument atIndex:2];
            NSString* updatedReturnValue = @"Unit Tests are important"
            [invocation setReturnValue:&updatedReturnValue];
        }] invocationAlternateMethod:@"Leela"];
        
    });
    
// Since we already setup mock above to return 40 when passed parameters 10 and 30, this test will pass.
    it(@"Verifying Mocking instance methods", ^{
        XCTAssert([operation addNumbers:10 andNum2:30] == 40);
    });
    
// We already mocked this method to return 3000 when values of 10 and 300 are passed. Thus this test will pass too.
    it(@"Verifying Mocking class methods", ^{
        XCTAssert([JKUnitTestsDemoOperations multiplyNumbers:10 andNum2:300] == 3000);
    });
    
// We already swapped the implementation of method 'myName' with another method 'alwaysReturns1000' which returns the string '1000'. This test will also pass.

    it(@"Verifying the method calling bypass", ^{
        XCTAssert([[operation myName] isEqualToString:@"1000"]);
    });
    
// We are executing the method 'invocationAlternateMethod' here. Since we already overrode the implementation with invoke block, which will return alternate value of 'Unit Tests are important', this test will pass too.
    it(@"Verifying Block delegate using OCMock", ^{
        XCTAssert([[operation invocationAlternateMethod:@"Leela"] isEqualToString:@"Unit Tests are important"], @"Failed");
    });
});

SpecEnd

This should be it for this post. In the next post I will show you how to test an asynchronous method.

Go to next article