0

I'm currently trying to set up a custom delegate on an async dataTaskWithRequest via NSURLSession. I've set up the protocol and implemented the delegate method, but I am stuck at figuring out whether I've implemented it correctly, and how to unit test it. Specifically, I'd like to test if the delegate returns something after it has been called, and test with a live API call. I've tried testing via the approach suggested here (OCUnit test for protocols/callbacks/delegate in Objective-C), but the test fails, probably because I'm missing something or am not taking into account the async call. Code of attempted delegate implementation and unit test are below.

Delegate protocol declaration:

#include "PtvApiPublic.h"

#ifndef PtvApiDelegate_h
#define PtvApiDelegate_h

@class PtvApi;
@protocol PtvApiDelegate <NSObject>
    -(void) ptvApiHealthCheck: (PtvApi *) sender;
@end

#endif /* PtvApiDelegate_h */

Header file:

#include "PtvApiDelegate.h"

#ifndef PtvApi_h
#define PtvApi_h

@interface PtvApi : NSObject
@property (nonatomic, weak) id <NSURLSessionDelegate> delegate;
- (void)ptvApiHealthCheck;
@end

#endif /* PtvApi_h */

Snippet of PtvApi.m

#import <Foundation/Foundation.h>
#import <CommonCrypto/CommonHMAC.h>
#import <CommonCrypto/CommonDigest.h>
#import "PtvApiPublic.h"
#import "PtvApiPrivate.h"
#import "PtvApiDelegate.h"

@implementation PtvApi
@synthesize delegate;

...

- (void)ptvApiHealthCheck
{
    NSString *fullUrl = [self GenerateRequestUrl];

    NSURLSession *apiSession = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:delegate delegateQueue:nil];
    NSURL *apiUrl = [NSURL URLWithString: fullUrl];
    NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:apiUrl];
    [apiSession dataTaskWithRequest:urlRequest];
}

@end

Unit test:

#import <XCTest/XCTest.h>

#import "PtvApiPublic.h"

@interface APIDelegateTests : XCTestCase <NSURLSessionDelegate>
{
    PtvApi *testApi;
    BOOL callbackInvoked;
}
@end

@implementation APIDelegateTests

- (void)setUp {
    [super setUp];
    testApi = [[PtvApi alloc] init];
    testApi.delegate = self;
}

- (void)tearDown {
    testApi.delegate = nil;
    [super tearDown];
}

- (void)testThatApiCallbackWorks {
    [testApi ptvApiHealthCheck];

    XCTAssert(callbackInvoked, @"Delegate should return something, I think...");
}
@end
Community
  • 1
  • 1

1 Answers1

2

Alright I figured it out myself with the help of Apple's documentation for NSURLSession and the blog at http://www.infinite-loop.dk/blog/2011/04/unittesting-asynchronous-network-access/. I've tested it with the live URL for now, I'll update the answer when I add API mocking to my unit test and make the unit test more robust.

The short of it is that NSURLSession has its own delegate methods, and in this case where dataTaskWithRequest is used, an NSURLSessionDataDelegate can be set up and used to retrieve the API results.

The code for the delegate declaration is mostly correct, I just needed to change NSURLSessionDelegate to NSURLSessionDataDelegate in the header file.

The unit test requires a bit of set up, but is otherwise pretty straightforward. It involves initialising the class with the NSURLSession call, setting the object's delegate to self, and initialise a flag variable to NO. The flag variable will be set to YES when the delegate is called, which is what I am initially testing for. The fully set up unit test is below.

@interface APIDelegateTests : XCTestCase <NSURLSessionDataDelegate>
{
    PtvApi *testApi;
    BOOL callbackInvoked;
}
@end

@implementation APIDelegateTests

- (void)setUp {
    [super setUp];
    testApi = [[PtvApi alloc] init];
    testApi.delegate = self;
    callbackInvoked = NO;
}

- (void)tearDown {
    testApi.delegate = nil;
    [super tearDown];
}

- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
{
    callbackInvoked = YES;
}

// Method is credit to Claus Brooch.
// Retrieved from http://www.infinite-loop.dk/blog/2011/04/unittesting-asynchronous-network-access/ on 10/04/2016
- (BOOL)waitForCompletion:(NSTimeInterval)timeoutSecs {
    NSDate *timeoutDate = [NSDate dateWithTimeIntervalSinceNow:timeoutSecs];

    do {
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:timeoutDate];
        if([timeoutDate timeIntervalSinceNow] < 0.0)
            break;
    } while (!callbackInvoked);

    return callbackInvoked;
}

- (void)testThatApiCallbackWorks {
    [testApi ptvApiHealthCheck];

    XCTAssert([self waitForCompletion:30.0], @"Testing to see what happens here...");
}
@end