iosnsurlsessionnsurlprotocolnsurlsessionconfiguration

Test that NSURLSessionConfiguration settings are obeyed


I created an NSURLSessionConfiguration with some default settings but when I see the request object made with that configuration in my custom NSURLProtocol it doesn't seem that all those settings are inherited and I'm a bit confused.

NSURLSessionConfiguration* config = [NSURLSessionConfiguration defaultSessionConfiguration];

NSMutableArray *protocolsArray = [NSMutableArray arrayWithArray:config.protocolClasses];

[protocolsArray insertObject:[CustomProtocol class] atIndex:0];

config.protocolClasses = protocolsArray;

// ex. set some random parameters

[config setHTTPAdditionalHeaders:@{@"Authorization":@"1234"}];
[config setAllowsCellularAccess:NO];
[config setRequestCachePolicy:NSURLRequestReturnCacheDataElseLoad];
[config setHTTPShouldSetCookies:NO];
[config setNetworkServiceType:NSURLNetworkServiceTypeVoice];
[config setTimeoutIntervalForRequest:4321];

// Create a request with this configuration and start a task

NSURLSession* session = [NSURLSession sessionWithConfiguration:config];

NSURLRequest* request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"https://google.com"]];

NSURLSessionDataTask* task = [session dataTaskWithRequest:request];

[task resume];

In my custom NSURLProtocol that is registered

- (void)startLoading {
    ...

    // po [self.request valueForHTTPHeaderField:@"Authorization"] returns 1234
    //
    // However, I'm very confused why
    //
    //  - allowsCellularAccess
    //  - cachePolicy
    //  - HTTPShouldHandleCookies
    //  - networkServiceType
    //  - timeoutInterval
    //
    // for the request return the default values unlike for the header

    ...

}

Is there some way to check that those parameters I've set are obeyed and inherited by the request?


Solution

  • When dealing with http requests, it is helpful to start with the basics, such as is the request actually being made by the OS, and is a response being received? This will in part help to answer your question about checking that set parameters are infact being obeyed by the request.

    I would challenge your use of the word "inherit" in the phrase

    Is there some way to check that those parameters I've set are obeyed and inherited by the request?

    Inheritance in Object Oriented programming has a very specific meaning. Did you in fact create a custom subclass (let's call it SubClassA) of NSURLRequest with specific properties, and then a further subclass (let's call it SubClassB), and are expecting the second subclass (SubClassB) to inherit properties from its parent (SubClassA)? If so, this is certainly not indicated in the code you provided.

    There are several HTTP Proxy programs available which help confirm whether or not the HTTP request is being sent, if a response is received, and also which allow you to inspect the details of the request and the response. Charles HTTP Proxy is one such program. Using Charles, I was able to determine that your code as provided is not making any HTTP request. So you cannot confirm or deny any parameters if the request is not being made.

    By commenting out the lines including the CustomProtocol as part of the NSURLSession configuration, and running your code either with or without these lines, I gained some potentially valuable information:

    1. by commenting out the lines including the CustomProtocol, a request was in fact made (and failed), as informed by Charles HTTP Proxy. I also added a completion block to your method dataTaskWithRequest. This completion block is hit when the CustomProtocol configuration lines are commented out. The CustomProtocol's startLoading method is not hit.
    2. when leaving in the original lines to configure the NSURLSession using the CustomProtocol, there was no request recorded by Charles HTTP Proxy, and the completion handler is not hit. However, the CustomProtocol's startLoading method is hit.

    Please see code below (modifications made to the code posted in the original question).

    NSURLSessionConfiguration* config = [NSURLSessionConfiguration defaultSessionConfiguration];
    
    NSMutableArray *protocolsArray = [NSMutableArray arrayWithArray:config.protocolClasses];
    
    //[protocolsArray insertObject:[CustomProtocol class] atIndex:0];
    
    //config.protocolClasses = protocolsArray;
    
    // ex. set some random parameters
    
    [config setHTTPAdditionalHeaders:@{@"Authorization":@"1234"}];
    [config setAllowsCellularAccess:NO];
    [config setRequestCachePolicy:NSURLRequestReturnCacheDataElseLoad];
    [config setHTTPShouldSetCookies:NO];
    [config setNetworkServiceType:NSURLNetworkServiceTypeVoice];
    [config setTimeoutIntervalForRequest:4321];
    
    // Create a request with this configuration and start a task
    
    NSURLSession* session = [NSURLSession sessionWithConfiguration:config];
    
    NSURLRequest* request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"https://google.com"]];
    
    NSURLSessionDataTask* task = [session dataTaskWithRequest:request
                                            completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
                                                NSString * auth = [request valueForHTTPHeaderField:@"Authorization"];
                                                NSLog(@"Authorization: %@", auth);
                                                BOOL allowsCellular = [request allowsCellularAccess];
                                                NSString * allowsCellularString = allowsCellular ? @"YES" : @"NO";
                                                NSLog(@"Allows cellular: %@", allowsCellularString);
    }];
    
    [task resume];
    

    This gives you the information that the CustomProtocol is not properly handling the request. Yes, the breakpoint inside the startLoading method is hit when the CustomProtocol is configured as part of the NSURLSession, but that is not definitive proof that the CustomProtocol is handling the request properly. There are many steps necessary to using a CustomProtocol, as outlined by Apple (Protocol Support, NSURLProtocol Class Reference) that you should confirm you are following.

    Some things to make sure are working:

    for example:

    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
        // Override point for customization after application launch.
        [NSURLProtocol registerClass:[CustomProtocol class]];
        return YES;
    }
    

    Below is a unit tests to verify that the NSURLSession is functioning as expected (without using our custom protocol explicitly). Note that this unit test does pass when added to Apple's own sample code for the project CustomHTTPProtocol, but does not pass using our very bare bones CustomProtocol

    - (void)testNSURLSession {
        XCTestExpectation *expectation = [self expectationWithDescription:@"Testing standard NSURL Session"];
    
        [[[NSURLSession sharedSession] dataTaskWithURL:[NSURL URLWithString:@"https://www.apple.com/"] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
    #pragma unused(data)
            XCTAssertNil(error, @"NSURLSession test failed with error: %@", error);
            if (error == nil) {
                NSLog(@"success:%zd / %@", (ssize_t) [(NSHTTPURLResponse *) response statusCode], [response URL]);
                [expectation fulfill];
            }
        }] resume];
    
        [self waitForExpectationsWithTimeout:3.0 handler:^(NSError * _Nullable error) {
            if(nil != error) {
                XCTFail(@"NSURLSession test failed with error: %@", error);
            }
        }];
    }
    

    Below is a unit test which may be used to verify that the configurations made to a NSURLSession are as expected, when configuring using our own CustomProtocol class. Again, please note that this test fails using the empty implementation of CustomProtocol but this is expected if using Test Driven Development (create the test first, and then the code second which will allow the test to pass).

    - (void)testCustomProtocol {
        XCTestExpectation *expectation = [self expectationWithDescription:@"Testing Custom Protocol"];
    
        NSURLSessionConfiguration* config = [NSURLSessionConfiguration defaultSessionConfiguration];
        NSMutableArray *protocolsArray = [NSMutableArray arrayWithArray:config.protocolClasses];
    
        [protocolsArray insertObject:[CustomProtocol class] atIndex:0];
    
        config.protocolClasses = protocolsArray;
    
        // ex. set some random parameters
    
        [config setHTTPAdditionalHeaders:@{@"Authorization":@"1234"}];
        [config setAllowsCellularAccess:NO];
        [config setRequestCachePolicy:NSURLRequestReturnCacheDataElseLoad];
        [config setHTTPShouldSetCookies:NO];
        [config setNetworkServiceType:NSURLNetworkServiceTypeVoice];
        [config setTimeoutIntervalForRequest:4321];
    
        // Create a request with this configuration and start a task
        NSURLSession* session = [NSURLSession sessionWithConfiguration:config];
        NSURLRequest* request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"https://www.apple.com"]];
    
        NSURLSessionDataTask* task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
    #pragma unused(data)
            XCTAssertNil(error, @"test failed: %@", error.description);
            if (error == nil) {
                NSLog(@"success:%zd / %@", (ssize_t) [(NSHTTPURLResponse *) response statusCode], [response URL]);
                NSString * auth = [request valueForHTTPHeaderField:@"Authorization"];
                NSLog(@"Authorization: %@", auth);
                XCTAssertNotNil(auth);
    
                BOOL allowsCellular = [request allowsCellularAccess];
                XCTAssertTrue(allowsCellular);
    
                XCTAssertEqual([request cachePolicy], NSURLRequestReturnCacheDataElseLoad);
    
                BOOL shouldSetCookies = [request HTTPShouldHandleCookies];
                XCTAssertTrue(shouldSetCookies);
    
                XCTAssertEqual([request networkServiceType], NSURLNetworkServiceTypeVoice);
    
                NSTimeInterval timeOutInterval = [request timeoutInterval];
                XCTAssertEqualWithAccuracy(timeOutInterval, 4321, 0.01);
                [expectation fulfill];
    
            }
    
        }];
    
        [task resume];
    
        [self waitForExpectationsWithTimeout:3.0 handler:^(NSError * _Nullable error) {
            if(nil != error) {
                XCTFail(@"Custom Protocol test failed with error: %@", error);
            }
        }];
    }