snaggs
snaggs

Reputation: 5713

Override class method behavior through unit testing iOS

I have class ServerHandler with one class method:

+ (void) sendRequestEventToServer:(MyData*) requestDataForCaching                   
                        onSuccess:(void (^)(void))successBlock;

This method is responsible to send HTTP request to Server. I call this method from dozen places in my program.

My goal is to get what data is sent to server therefore I need to write test where I override this method and instead to send MyData to server my program should send it to Test class.

I tried several approaches but none of them works.

@interface ServerHandler (TestSupport)
+ (void) mockSendRequestEventToServer:(MyData*) requestDataForCaching onSuccess:(void (^)(void))successBlock;
@end

@implementation ServerHandler (TestSupport)

+ (void) mockSendRequestEventToServer:(MyData*) requestDataForCaching  onSuccess:(void (^)(void))successBlock
{
    NSLog(@"mockSendRequestEventToServer");
}

+ (void)load {
    Method original = class_getClassMethod([ServerHandler class], @selector(sendRequestEventToServer));
    Method mocked   = class_getClassMethod([ServerHandler class], @selector(mockSendRequestEventToServer));
    method_exchangeImplementations(original, mocked);
}
@end

Now I run the test:

@implementation ServerTalkerTests

- (void)setUp {
    [super setUp];    
    [ServerHandler load];
}


- (void) test_serverTalker {

     __block BOOL isRunning = YES;

    [[MyProgram shared] start]; 

     // ...
    NSDate *timeout = [NSDate dateWithTimeIntervalSinceNow:10];
    do {
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
                                 beforeDate:timeout];
    } while (isRunning);

}

@end

When I run the program through [[MyProgram shared] start]; It should call mocked [ServerHandler sendRequestEventToServer] but it calls original method

What Im doing wrong?

I also tried with OCMock

Upvotes: 1

Views: 294

Answers (1)

iphonic
iphonic

Reputation: 12717

Follow the code below, should execute your methods properly.

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];

        SEL originalSelector = @selector(sendRequestEventToServer);
        SEL mockedSelector = @selector(mockSendRequestEventToServer);

        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        Method mockedMethod = class_getInstanceMethod(class, mockedSelector);


        BOOL didAddMethod =
            class_addMethod(class,
                originalSelector,
                method_getImplementation(mockedMethod),
                method_getTypeEncoding(mockedMethod));

        if (didAddMethod) {
            class_replaceMethod(class,
                mockedSelector,
                method_getImplementation(originalMethod),
                method_getTypeEncoding(originalMethod));
        } else {
            method_exchangeImplementations(originalMethod, mockedMethod);
        }
    });
}

Source - Method Swizzling

Update - Using jrswizzle I solved it

Add your new method in the same class ie ServerHandler not in a category, and just call the following method

[ServerHandler jr_swizzleClassMethod:@selector(sendRequestEventToServer) withClassMethod:@selector(mockSendRequestEventToServer) error:nil];

Then call the original method

[ServerHandler sendRequestEventToServer:nil completion:nil];

The above should work fine.

Cheers.

Upvotes: 1

Related Questions