Using OCMock to create iOS Unit Tests

Unit tests in brief

Unit testing is great because it enables you experiment, debug, and monitor the results of your application’s features.

XCode has a built-in in support for unit testing. But still, if you wish to test a certain task, or object, using XCode’s unit test framework only, you will have to create a real object with ALL its dependencies!.. A Hassle!

However, with the aid of OCMock, things have been made easier. OCMock is an open source framework that provides Objective-C implementation of mock objects. Mocks are fake objects that can behave and stand-in for concrete real objects.

Before going further, you need to…

  1. Download the framework http://ocmock.org/download/
  2. Add it to your project http://ocmock.org/ios/

The basics of OCMock will not be covered in this post as they are already covered in OCMock’s website. Throughout this post we will be going through the steps you need to make in order to conduct unit test for simple synchronous tasks, then we will elaborate in more details how to write unit tests for asynchronous tasks.

In general, here are the steps:

1-   Create a mock object.
2-   Make expectations.
3-   Execute the code under test.
4-   Verify your expectations.

1-Create a mock object:

OCMock provides several mock types; Mocks, nice mocks and partial mocks. You can create a mock object using one of the factory methods available on the OCMockObject  class.

To create a mock:

id mockPlace = [OCMockObject mockForClass:[Place class]];

To create a nice mock:

id niceMockPlace = [OCMockObject niceMockForClass:[Place class]];

The main difference between Mocks and Nice mocks is how they behave when expectations are not met. Mocks will raise an exception while nice mocks will ignore the exception and continue execution.

The last type of mocks is Partial mocks. Unlike mocks and nice mocks, to create a partial mock for an object you will first need to create an instance of the actual object. Because, for this type, when an unexpected method is called it gets passed to the real object. However, this has some pros and cons.

Pros are:
1-   It is useful when the object under test has several methods and you only need to invoke few of them.
2-   Invoking an unexpected method will not raise an exception.

Cons are:
1-   It violates the principle of Mock objects by “partially” relying on the real objects and their dependencies.

Here is how to create a partial mock object:

Place *actualPlaceInstance = [[Placealloc] init];

id partialMockPlace = [OCMockObjectpartialMockForObject:actualPlaceInstance];

Lets move to the next step…

2-Make expectations:

Sending expectations to mock objects can be done by calling either –expect or –stub methods. These methods return an object on which you can directly invoke your expected methods. Here is how it works:

Lets assume that in our “Place” class we want to get the place’s details from Google Places API given the place’s name

In Place.h:

@interface Place : NSObject
@property NSString *name;
-(NSString *)getPlaceName;
-(NSString *)getEncodedName;
-(NSString *)encodeName;
@end

In Place.m:

@implementation Place
@synthesize name;
-(NSString *)getPlaceName
{
    return self.name;
}

-(NSString *)encodeName
{
    return [[self getPlaceName] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
}

-(NSString *)getEncodedName
{
    return [self encodeName];
}

-(void)getPlaceDetails:(void (^)())successBlock
{
    //Make a network call to get the place's details from Google Places API for instance
}
In PlaceTest.m:
//using -stub
-(void)testGetPlaceDetails
{
    Place *actualPlaceInstance = [[Place alloc] init];
    id partialMockPlace = [OCMockObject partialMockForObject:actualPlaceInstance];
    [[[partialMockPlace stub] andReturn:@"JFK International Airport"] getPlaceName];

    //Assert that getPlaceName's return is equal to "JFK International Airport"
    XCTAssertEqualObjects([partialMockPlace getPlaceName], @"JFK International Airport",@"Invalid Place name");
}

Both –stub and –expect allow you to force a method to return a certain value whenever called during your test so you don’t have to worry about its result. In the example above, I am forcing the getPlaceName to return @”JFK International Airport”.

So, what is the difference between –stub and –expect?

The main difference is: you –expect methods that must happen, and –stub methods that might happen.

There are 2 ways mock objects fail: either an unexpected/unstubbed method is called, or an expected method is not called:

  1. Unexpected invocations. When a mock object receives a message that hasn’t been either stubbed or expected, it throws an exception immediately and your test fails.
  2. Expected invocations. When you call verify on your mock (generally at the end of your test), it checks to make sure all of the methods you expected were actually called. If any were not, your test will fail.

3- Then comes the Assertions step.

 If you are using Apple’s XCTest Framework, then you can add assertions to your test. Here is a list of the available assertions:

  • XCTFail (format…)
  • XCTAssertNil (a1, format…)
  • XCTAssertNotNil (a1, format…)
  • XCTAssert (a1, format…)
  • XCTAssertTrue (a1, format…)
  • XCTAssertFalse (a1, format…)
  • XCTAssertEqualObjects (a1, a2, format…)
  • XCTAssertEquals (a1, a2, format…)
  • XCTAssertEqualsWithAccuracy (a1, a2, accuracy, format…)
  • XCTAssertThrows (expression, format…)
  • XCTAssertThrowsSpecific (expression, specificException, format…)
  • XCTAssertThrowsSpecificNamed (expression, specificException, exceptionName, format…)
  • XCTAssertNoThrow (expression, format…)
  • XCTAssertNoThrowSpecific (expression, specificException, format…)
  • XCTAssertNoThrowSpecificNamed (expression, specificExcepton, exceptionName, format…)

4-Execute a code

Now, lets make the test a little bit harder. We are not going to force a return value. Instead, we will just set an expectation and execute a piece of code

In PlaceTest.m:

-(void)testGetPlaceDetails
{
    Place *actualPlaceInstance = [[Place alloc] init];
    id partialMockPlace = [OCMockObject partialMockForObject:actualPlaceInstance];

    NSString *name = @"JFK International Airport";
    [[partialMockPlace expect] encodeName:name]; //expect encodeName to be called

    [actualPlaceInstance getEncodedName:name]; //call getEncodedName on the actual object

    [partialMockPlace verify]; //verify your test!
}

Next…

5-Verify

After setting expectations comes the last step that you must make. Otherwise your test will falsely pass; actually, it won’t be effective.

Testing Callback blocks

This is almost everything you need to know about running simple tests. Lets take the complexity a little bit higher and see how to test callback blocks, which can be used to test network calls.

We can do so by simply defining a Boolean value, completion block and a waiting time interval.

In Place.m:

-(void)getPlaceDetails:(void (^)())successBlock
{
    //Make a network call to get the place's details from Google Places API for instance
}

In PlaceTest.m:

-(void)testGetPlaceDetails
{
    Place *actualPlaceInstance = [[Place alloc] init];
    id partialMockPlace = [OCMockObject partialMockForObject:actualPlaceInstance];

    __block BOOL hasCalledBack = NO;

    void (^completionBlock)(id result) = ^(id result){

        NSLog(@"Completion Block!");
        hasCalledBack = YES;
        //Process result
    };

    [partialMockPlace getPlaceDetails:completionBlock];

    //This is what the next piece of code does:
    //1- Wait until the results come.. This code will wait for 20 seconds.
    //2- Check for the hasCalledBack value; if it is true this mean that the completion block has been    executed. If not, it either means that the network call failed and has been terminated or it still     needs more time, in this case increase the waiting time. You can also define a failureBlock to be called when the NSURLConnectionDelegate's didFailWithError method is triggered
    //3- Assert that hasCalledBack is equal to NO.
    //4- Verify the test

    NSDate *loopUntil = [NSDate dateWithTimeIntervalSinceNow:20];//1

    while (hasCalledBack == NO && [loopUntil timeIntervalSinceNow] > 0) {

        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:loopUntil];

    } //2

    XCTAssertEqual(hasCalledBack, NO, @"Test failed. Either because the waiting time is not long enough or because the NSURLConnection has failed"); //3

    [partialMockPlace verify]; //4
}

Where to go next?

Check those references for more details on unit testing and OCMock framework:

One thought to “Using OCMock to create iOS Unit Tests”

  1. Howdy! I could have sworn I’ve been to your blog before but after browsing through a few of
    the articles I realized it’s new to me. Nonetheless,
    I’m certainly pleased I found it and I’ll be book-marking
    it and checking back often!

Leave a Reply

Your email address will not be published. Required fields are marked *