how to unit testing AFNetworking request

2019-01-24 09:20发布

问题:

i am making a GET request to retrieve JSON data with AFNetworking as this code below :

        NSURL *url = [NSURL URLWithString:K_THINKERBELL_SERVER_URL];
    AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:url];
    Account *ac = [[Account alloc]init];
    NSMutableURLRequest *request = [httpClient requestWithMethod:@"GET" path:[NSString stringWithFormat:@"/user/%@/event/%@",ac.uid,eventID]  parameters:nil];

    AFHTTPRequestOperation *operation = [httpClient HTTPRequestOperationWithRequest:request
                                                                            success:^(AFHTTPRequestOperation *operation, id responseObject) {
                                                                                NSError *error = nil;
                                                                                NSDictionary *JSON = [NSJSONSerialization JSONObjectWithData:responseObject options:NSJSONReadingAllowFragments error:&error];
                                                                                if (error) {
                                                                                }

                                                                                [self.delegate NextMeetingFound:[[Meeting alloc]init] meetingData:JSON];

                                                                            }
                                                                            failure:^(AFHTTPRequestOperation *operation, NSError *error){
                                                                            }];
    [httpClient enqueueHTTPRequestOperation:operation];

the thing is i want to create a unit test based on this data, but i dont want that the test will actually make the request. i want a predefined structure will return as the response. i am kind'a new to unit testing, and poked a little of OCMock but cant figure out how to manage this.

回答1:

Several things to comment about your question. First of all, your code is hard to test because it is creating the AFHTTPClient directly. I don't know if it's because it's just a sample, but you should inject it instead (see the sample below).

Second, you are creating the request, then the AFHTTPRequestOperation and then you enqueue it. This is fine but you can get the same using the AFHTTPClient method getPath:parameters:success:failure:.

I do not have experience with that suggested HTTP stubbing tool (Nocilla) but I see it is based on NSURLProtocol. I know some people use this approach but I prefer to create my own stubbed response objects and mock the http client like you see in the following code.

Retriever is the class we want to test where we inject the AFHTTPClient. Note that I am passing directly the user and event id, since I want to keep things simple and easy to test. Then in other place you would pass the accout uid value to this method and so on... The header file would look similar to this:

#import <Foundation/Foundation.h>

@class AFHTTPClient;
@protocol RetrieverDelegate;

@interface Retriever : NSObject

- (id)initWithHTTPClient:(AFHTTPClient *)httpClient;

@property (readonly, strong, nonatomic) AFHTTPClient *httpClient;

@property (weak, nonatomic) id<RetrieverDelegate> delegate;

- (void) retrieveEventWithUserId:(NSString *)userId eventId:(NSString *)eventId;

@end


@protocol RetrieverDelegate <NSObject>

- (void) retriever:(Retriever *)retriever didFindEvenData:(NSDictionary *)eventData;

@end

Implementation file:

#import "Retriever.h"
#import <AFNetworking/AFNetworking.h>

@implementation Retriever

- (id)initWithHTTPClient:(AFHTTPClient *)httpClient
{
    NSParameterAssert(httpClient != nil);

    self = [super init];
    if (self)
    {
        _httpClient = httpClient;
    }
    return self;
}

- (void)retrieveEventWithUserId:(NSString *)userId eventId:(NSString *)eventId
{
    NSString *path = [NSString stringWithFormat:@"/user/%@/event/%@", userId, eventId];

    [_httpClient getPath:path
              parameters:nil
                 success:^(AFHTTPRequestOperation *operation, id responseObject)
    {
        NSDictionary *eventData = [NSJSONSerialization JSONObjectWithData:responseObject options:0 error:NULL];
        if (eventData != nil)
        {
            [self.delegate retriever:self didFindEventData:eventData];
        }
    }
                 failure:nil];
}

@end

And the test:

#import <XCTest/XCTest.h>
#import "Retriever.h"

// Collaborators
#import <AFNetworking/AFNetworking.h>

// Test support
#import <OCMock/OCMock.h>

@interface RetrieverTests : XCTestCase

@end

@implementation RetrieverTests

- (void)setUp
{
    [super setUp];
    // Put setup code here; it will be run once, before the first test case.
}

- (void)tearDown
{
    // Put teardown code here; it will be run once, after the last test case.
    [super tearDown];
}

- (void) test__retrieveEventWithUserIdEventId__when_the_request_and_the_JSON_parsing_succeed__it_calls_didFindEventData
{
    // Creating the mocks and the retriever can be placed in the setUp method.
    id mockHTTPClient = [OCMockObject mockForClass:[AFHTTPClient class]];

    Retriever *retriever = [[Retriever alloc] initWithHTTPClient:mockHTTPClient];

    id mockDelegate = [OCMockObject mockForProtocol:@protocol(RetrieverDelegate)];
    retriever.delegate = mockDelegate;

    [[mockHTTPClient expect] getPath:@"/user/testUserId/event/testEventId"
                          parameters:nil
                             success:[OCMArg checkWithBlock:^BOOL(void (^successBlock)(AFHTTPRequestOperation *, id))
    {
        // Here we capture the success block and execute it with a stubbed response.
        NSString *jsonString = @"{\"some valid JSON\": \"some value\"}";
        NSData *responseObject = [jsonString dataUsingEncoding:NSUTF8StringEncoding];

        [[mockDelegate expect] retriever:retriever didFindEventData:@{@"some valid JSON": @"some value"}];

        successBlock(nil, responseObject);

        [mockDelegate verify];

        return YES;
    }]
                             failure:OCMOCK_ANY];

    // Method to test
    [retriever retrieveEventWithUserId:@"testUserId" eventId:@"testEventId"];

    [mockHTTPClient verify];
}

@end

The last thing to comment is that the AFNetworking 2.0 version is released so consider using it if it covers your requirements.