Wednesday, December 23, 2015

Improve Unit Test with OCMock

Adding OCMock to your project:

  • Step 1: Download a release from the downloads page.
  • Step 2: Setup OCMock from the iOS page.
  • Step 3: Add an import to your unit tests.
#import <OCMock/OCMock.h>

Contents:

  • 1. Example classes

Let's assume we are writing a little application. Our application have two classes as ClassA and ClassB. ClassB inherit from ClassA. They have some methods as below:
#import <Foundation/Foundation.h>
static NSString* const NOTIFICATION_OF_CLASS = @"NotificationOfClass";
@class ClassA;
@class ClassB;
@protocol ClassADelegate <NSObject>
- (void)responsedValue:(ClassA*)class value:(NSString*)aValue;
@end
@interface ClassA : NSObject {
@public
ClassB* publicClass;
}
@property (nonatomic, assign) id delegate;
- (void)executeVoidMethod;
- (void)executeVoidMethodAndSubVoidMethod;
- (void)executeVoidMethodAndReturnMethod;
- (void)executeBlockMethod:(void(^)(NSString* returnedValue))aBlock;
- (NSString*)executeReturnMethodAndSubBlock;
- (void)executeVoidMethodAndDelegate;
- (void)executeVoidMethodAndNotification;
- (void)executeVoidMethodAndException:(NSString*)aParam;
- (void)executeRejectMethod;
@end
view raw ClassA_h hosted with ❤ by GitHub
#import "ClassA.h"
#import "ClassB.h"
@implementation ClassA
- (void)executeVoidMethod {
NSLog(@"Void_%@",[self class]);
}
- (void)executeVoidMethodAndSubVoidMethod {
if (publicClass) {
//Comment this code and run "testSubVoidMethod_UsingClassMock"
[publicClass executeVoidMethod];
//End
}
}
- (void)executeVoidMethodAndReturnMethod {
if (publicClass) {
//You can comment these codes and run "testStubActionsAndExpect_UsingClassMock"
NSString* value = [publicClass executeReturnMethod];
NSLog(@"%@",value);
//End
}
}
- (void)executeBlockMethod:(void(^)(NSString* returnedValue))aBlock {
aBlock([NSString stringWithFormat:@"Block_%@",[self class]]);
}
- (NSString*)executeReturnMethodAndSubBlock {
__block NSString* value = @"DefaultValue";
if (publicClass) {
[publicClass executeBlockMethod:^(NSString *returnedValue) {
value = returnedValue;
}];
}
return value;
}
- (void)simulateDelegate {
if ([_delegate respondsToSelector:@selector(responsedValue:value:)]) {
[_delegate responsedValue:self value:@"DelegateValue"];
}
}
- (void)executeVoidMethodAndDelegate {
[self performSelector:@selector(simulateDelegate)
withObject:nil afterDelay:2];
}
- (void)executeVoidMethodAndNotification {
[[NSNotificationCenter defaultCenter] postNotificationName:NOTIFICATION_OF_CLASSA
object:nil];
}
- (void)simulateException:(NSString*)aParam {
if (!aParam) {
[[NSException exceptionWithName:@"ABC" reason:@"Non" userInfo:nil] raise];
}
NSLog(@"Run fine");
}
- (void)executeVoidMethodAndException:(NSString*)aParam {
@try {
if (!publicClass) {
[[NSException exceptionWithName:@"Object" reason:@"Non" userInfo:nil] raise];
}
[publicClass simulateException:aParam];
}
@catch (NSException *exception) {
NSLog(@"%@",exception);
}
@finally {
NSLog(@"Final");
}
}
- (void)executeRejectMethod {
if (publicClass) {
//Uncomment this code and run "testRejectActions_UsingClassMock"
// [publicClass executeReturnMethod];
}
}
@end
view raw ClassA_m hosted with ❤ by GitHub
#import <Foundation/Foundation.h>
#import "ClassA.h"
@interface ClassB : ClassA
- (NSString*)executeReturnMethod;
- (void)executeMethodWithOrder1;
- (void)executeMethodWithOrder2;
@end
view raw ClassB_h hosted with ❤ by GitHub
#import "ClassB.h"
@implementation ClassB
- (NSString*)executeReturnMethod {
return @"ClassB";
}
- (void)executeMethodWithOrder1 {
NSLog(@"First");
}
- (void)executeMethodWithOrder2 {
NSLog(@"Second");
}
- (void)executeRejectMethod {
//Uncomment this code and run "testRejectActions_UsingPartialMock"
// [self executeReturnMethod];
//End
}
@end
view raw ClassB_m hosted with ❤ by GitHub

  • 2. Expect-run-verify

This is the original approach to mocking. First the mock object is set up with expectations, then the code under test is run, and afterwards the expectations are verified. If an expected method has not been invoked, or has not been invoked with the right arguments, then an error is reported. As shown it is possible to use argument constraints in the expect statement. Strict mocks can be created for classes and protocols.
Example codes:
//Definition of the category Test on the classA
@interface ClassA (PrivateMethodTest)
- (void)simulateException:(NSString*)aParam;
@end
- (void)testExpectRunVerify_UsingClassMock {
//GIVEN
ClassA* object = [[ClassA alloc] init];
id classMock = OCMClassMock([ClassB class]);
OCMExpect([classMock simulateException:@"@congpc.ios"]);
object->publicClass = classMock;
//WHEN /* run code under test */
[object executeVoidMethodAndException:@"@congpc.ios"];
//THEN
OCMVerifyAll(classMock);
}
- (void)testExpectRunVerify_UsingStrictMock {
//GIVEN
ClassA* object = [[ClassA alloc] init];
id strictMock = OCMStrictClassMock([ClassB class]);
OCMExpect([strictMock simulateException:@"@congpc.ios"]);
object->publicClass = strictMock;
//WHEN /* run code under test */
[object executeVoidMethodAndException:@"@congpc.ios"];
//THEN
OCMVerifyAll(strictMock);
}
view raw ExpectRunVerify hosted with ❤ by GitHub

  • 3. Verify after running

Verifies that some method has been called by the code under test. If the method has not been invoked an error is reported. In Xcode and AppCode the error is reported on the line of the verify, for other test environments an exception is thrown.
Base on the example of "Expect-Run-Verify", I rewrite as below:
- (void)testVerifyAfterRunning_UsingClassMock {
//GIVEN
ClassA* object = [[ClassA alloc] init];
id classMock = OCMClassMock([ClassB class]);
object->publicClass = classMock;
//WHEN /* run code under test */
[object executeVoidMethodAndException:@"@congpc.ios"];
//THEN
OCMVerify([classMock simulateException:@"@congpc.ios"]);
}
- (void)testVerifyAfterRunning_UsingStrictMock {
//GIVEN
ClassA* object = [[ClassA alloc] init];
id classMock = OCMStrictClassMock([ClassB class]);
object->publicClass = classMock;
//WHEN /* run code under test */
[object executeVoidMethodAndException:@"@congpc.ios"];
//THEN
OCMVerify([classMock simulateException:@"@congpc.ios"]);
}

  • 4. Void-Method

You can use ClassMock, PartialMock for test void method.
#pragma mark - Using OCMock for void method
- (void)testVoidMethod_UsingClassMock {
//GIVEN
id objectMock = OCMClassMock([ClassA class]);
//WHEN /* run code under test */
[objectMock executeVoidMethod];
//THEN
OCMVerify([objectMock executeVoidMethod]);
}
- (void)testVoidMethod_UsingClassMockAndExpect {
//GIVEN
id objectMock = OCMClassMock([ClassA class]);
[[objectMock expect] executeVoidMethod];
//WHEN /* run code under test */
[objectMock executeVoidMethod];
//THEN
OCMVerifyAll(objectMock);
}
- (void)testVoidMethod_UsingPatialMock {
//GIVEN
ClassA* object = [[ClassA alloc] init];
id objectMock = OCMPartialMock(object);
//WHEN /* run code under test */
[object executeVoidMethod];
//THEN
OCMVerify([objectMock executeVoidMethod]);
}
- (void)testVoidMethod_UsingPatialMockAndExpect {
//GIVEN
ClassA* object = [[ClassA alloc] init];
id objectMock = OCMPartialMock(object);
[[objectMock expect] executeVoidMethod];
//WHEN /* run code under test */
[object executeVoidMethod];
//THEN
OCMVerifyAll(objectMock);
}
#pragma mark Sub void method
- (void)testSubVoidMethod_UsingClassMock {
//GIVEN
ClassA* object = [[ClassA alloc] init];
id objectMock = OCMClassMock([ClassB class]);
object->publicClass = objectMock;
//WHEN /* run code under test */
[object executeVoidMethodAndSubVoidMethod];
//THEN
OCMVerify([objectMock executeVoidMethod]);
}
- (void)testSubVoidMethod_UsingClassMockAndExpect {
//GIVEN
ClassA* object = [[ClassA alloc] init];
id objectMock = OCMClassMock([ClassB class]);
[[objectMock expect] executeVoidMethod];
object->publicClass = objectMock;
//WHEN /* run code under test */
[object executeVoidMethodAndSubVoidMethod];
//THEN
OCMVerifyAll(objectMock);
}
view raw VoidMethod hosted with ❤ by GitHub

  • 5. Return-Method

You can use ClassMock, PartialMock for simulate the value is returned.
- (void)testReturnMethod_UsingClassMockAndStub {
//GIVEN
NSString* inputedValue = @"Simulated value";
id objectMock = OCMClassMock([ClassB class]);
OCMStub([objectMock executeReturnMethod]).andReturn(inputedValue);
//WHEN /* run code under test */
NSString* returnedValue = [objectMock executeReturnMethod];
//THEN
XCTAssertEqualObjects(inputedValue, returnedValue);
}
- (void)testReturnMethod_UsingPartialMockAndStub {
//GIVEN
NSString* inputedValue = @"Simulated value";
ClassB* object = [[ClassB alloc] init];
//You can comment these codes and run again.
id objectMock = OCMPartialMock(object); //Real object
OCMStub([objectMock executeReturnMethod]).andReturn(inputedValue);
//End comment
//WHEN /* run code under test */
NSString* returnedValue = [object executeReturnMethod];
//THEN
XCTAssertEqualObjects(inputedValue, returnedValue);
}
view raw ReturnMethod hosted with ❤ by GitHub

  • 6. Block

You can use ClassMock, PartialMock, andDo for simulate the value is returned from a block.
- (void)testBlockMethod_UsingClassMock {
//GIVEN
NSString* expectedValue = @"@congpc.ios";
ClassA* object = [[ClassA alloc] init];
//Simulate returned value of a block. You can comment these codes and run again.
id objectMock = OCMClassMock([ClassB class]);
[[[objectMock stub] andDo:^(NSInvocation *invocation) {
void (^successBlock)(NSString* value);
//Remember that self and _cmd are arguments 0 and 1.
[invocation getArgument:&successBlock atIndex:2];
successBlock(@"@congpc.ios");
}] executeBlockMethod:OCMOCK_ANY];
object->publicClass = objectMock;
//End comment
//WHEN /* run code under test */
NSString* returnedValue = [object executeReturnMethodAndSubBlock];
//THEN
XCTAssertEqualObjects(expectedValue, returnedValue);
}
view raw Block hosted with ❤ by GitHub

  • 7. Delegate and DataSource

You can use ProtocolMock for simulate a delegate or a datasource.
- (void)testDelegate_UsingClassMockAndExpect_VerifyWithDelay {
//GIVEN
ClassA* object = [[ClassA alloc] init];
id delegateMock = OCMProtocolMock(@protocol(ClassADelegate));
// set an expectation
[[delegateMock expect] responsedValue:object value:@"DelegateValue"];
object.delegate = delegateMock;
//WHEN
[object executeVoidMethodAndDelegate];
//THEN
OCMVerifyAllWithDelay(delegateMock, 2);
}
view raw Delegate hosted with ❤ by GitHub
Simulate the datasource of TableView
- (void)testDataSourceOfTableView {
// create the code under test
UITableView *tableView = [[UITableView alloc] init];
// create a protocol mock
id mockDataSource = OCMProtocolMock(@protocol(UITableViewDataSource));
// stub a protocol query
[OCMStub([mockDataSource numberOfSectionsInTableView:tableView]) andReturnValue:[NSNumber numberWithInteger:4]];
// give the mock to the code under test
[tableView setDataSource:mockDataSource];
// execute the code under test
NSInteger n = [tableView numberOfSections];
// verify results
XCTAssertEqual(4, n, @"should have 4 sections");
}
view raw DataSource hosted with ❤ by GitHub

  • 8. Notification (Observer)

You can use ObserverMock for test NotificationCenter.
- (void)testNotification_UsingObserverMockAndExpect {
//GIVEN
ClassA* object = [[ClassA alloc] init];
id observerMock = OCMObserverMock();
// set an expectation
OCMExpect([observerMock notificationWithName:NOTIFICATION_OF_CLASS
object:nil]);
// register the mock observer with an NSNotificationCenter
[[NSNotificationCenter defaultCenter] addMockObserver:observerMock
name:NOTIFICATION_OF_CLASS
object:nil];
//WHEN
[object executeVoidMethodAndNotification];
//THEN
OCMVerifyAll(observerMock);
}
- (void)testNotification_UsingClassMockAndObserverMockAndStubAndPost {
//GIVEN
id observerMock = OCMObserverMock();
NSNotification* aNotification = [observerMock notificationWithName:NOTIFICATION_OF_CLASS
object:nil];
ClassA* object = [[ClassA alloc] init];
id classMock = OCMClassMock([ClassB class]);
OCMStub([classMock executeVoidMethod]).andPost(aNotification);
object->publicClass = classMock;
// register the mock observer with an NSNotificationCenter
[[NSNotificationCenter defaultCenter] addMockObserver:observerMock
name:NOTIFICATION_OF_CLASS
object:nil];
//WHEN
[object executeVoidMethodAndSubVoidMethod];
//THEN
OCMVerifyAll(classMock);
OCMVerifyAll(observerMock);
}

  • 9. Exception

You can use StrictClassMock for test Exception.
- (void)testException_UsingStrictMock_FailingFast {
//GIVEN
id strictMock = OCMStrictClassMock([ClassB class]);
//WHEN
@try {
[strictMock executeVoidMethodAndException:@"@congpc.ios"];// this will throw an exception
}
@catch (NSException *exception) {
NSLog(@"%@",exception);
}
@finally {
NSLog(@"Final");
}
//THEN
OCMVerify([strictMock executeVoidMethodAndException:@"@congpc.ios"]);
XCTAssertThrows([strictMock executeVoidMethodAndException:@"@congpc.ios"]);// this will throw an exception
}
- (void)testException_UsingClassMockAndThrow {
//GIVEN
//Change NSGenericException to NSRangeException and run again.
NSException* anException = [NSException exceptionWithName:NSGenericException reason:@"" userInfo:nil];
ClassA* object = [[ClassA alloc] init];
id classMock = OCMClassMock([ClassB class]);
OCMStub([classMock executeVoidMethod]).andThrow(anException);
object->publicClass = classMock;
//WHEN + THEN
XCTAssertThrowsSpecificNamed([object executeVoidMethodAndSubVoidMethod], NSException, NSGenericException);
}
view raw Exception hosted with ❤ by GitHub

  • 10. Advanced topics

Verifying in order:The mock can be told to verify that expected methods are called in the same order as the expectations are set up. As soon as a method is called that is not next on the “expected list” the mock will fail fast and throw an exception.
- (void)testVerifyingInOrder_UsingStrictMock {
//GIVEN
id strictMock = OCMStrictClassMock([ClassB class]);
[strictMock setExpectationOrderMatters:YES];
//WHEN
OCMExpect([strictMock executeMethodWithOrder1]); // First
OCMExpect([strictMock executeMethodWithOrder2]); // Second
//THEN
[strictMock executeMethodWithOrder1];//Comment this code and run again.
// calling executeMethodWithOrder2 before executeMethodWithOrder1 will cause an exception to be thrown
[strictMock executeMethodWithOrder2];
}
Using reject method:
- (void)testRejectActions_UsingClassMock {
//GIVEN
ClassA* object = [[ClassA alloc] init];
id classMock = OCMClassMock([ClassB class]);
[[classMock reject] executeReturnMethod];
object->publicClass = classMock;
//WHEN + THEN
[object executeRejectMethod];
}
- (void)testRejectActions_UsingPartialMock {
//GIVEN
ClassB* object = [[ClassB alloc] init];
id classMock = OCMPartialMock(object);
[[classMock reject] executeReturnMethod];
//WHEN + THEN
[object executeRejectMethod];
}
view raw RejectAction hosted with ❤ by GitHub
Using expect and return:
- (void)testStubActionsAndExpect_UsingClassMock {
//GIVEN
ClassA* object = [[ClassA alloc] init];
id classMock = OCMClassMock([ClassB class]);
OCMExpect([classMock executeReturnMethod]).andReturn(@"@congpc.ios");
object->publicClass = classMock;
//WHEN /* run code under test */
[object executeVoidMethodAndReturnMethod];
//THEN
OCMVerifyAll(classMock);
}
view raw ExpectAndReturn hosted with ❤ by GitHub
Test to open an url:
- (void)testOpenUrl {
ViewController *toTest = [[ViewController alloc] init];
NSURL *toOpen = [NSURL URLWithString:@"http://www.google.com"];
// Create a partial mock of UIApplication
id mockApplication = [OCMockObject partialMockForObject:[UIApplication sharedApplication]];
// Set an expectation that the UIApplication will be told to open the url
[[mockApplication expect] openURL:toOpen];
[toTest launchURL:toOpen]; //Test private method
[mockApplication verify];
[mockApplication stopMocking];
}
view raw OpenURL hosted with ❤ by GitHub

Tools: XCode 7, OCMock 3.0

Reference documents:

- OCMock
- Engineering
- StackOverflow

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.