Writing mock tests for AFNetworking

Mocking is not new and in the iOS space there is a nice library called OCMock. For HTTP communication the best library is AFNetworking. Both frameworks have some documentation on their project page, so I am not repeating basics here. For OCMock I also liked this article by Alex Vollmer.

Mock the HTTP access

Recently I had the requirement to mock the HTTP inside of “service classes”, that use the AFHTTPClient class for the network job. What I wanted to do is basically mock the (JSON) result of the HTTP GET request. With OCMock, it’s not that hard:

// create a mock of the AFHTTPClient:
id mockClient = [OCMockObject mockForClass:[AFHTTPClient class]];

// 1) build the expectations
//  * we expect that the "getPath" is invoked, once!
//  * here we don't care too much about the passed in arguments...
[[mockClient expect]
   getPath:[OCMArg isNotNil]
   parameters:[OCMArg isNil]
   success:[OCMArg isNotNil]
   failure:[OCMArg isNotNil]] ;

// 2) run the actual test:
    [mockClient getPath:@"projects"
       parameters:nil
       success:^(AFHTTPRequestOperation *operation, id responseObject) {
          NSLog(@"SUCCESS");
       }
       failure:^(AFHTTPRequestOperation *operation, NSError *error) {
           NSLog(@"FAIL");
    }];

Ok, that’s nice and the test passes – but none of the give blocks has been invoked.. In order to fake (->mock) a JSON response, we need to make sure that our ‘success’ block is invoked. That’s quite simple with OCMock. The library offers an¬†andDo¬†method, were we are able to get access to all the agruments of the later invocation of the “getPath” method from the AFHTTPClient class:

// create a mock of the AFHTTPClient:
id mockClient = [OCMockObject mockForClass:[AFHTTPClient class]];

// 1) build the expectations
//  * we expect that the "getPath" is invoked, once!
//  * here we don't care too much about the passed in arguments...
//  * we use the andDo function to invoke the GIVEN (notNil) success block with our mocked JSON
[[[mockClient expect] andDo:^(NSInvocation *invocation) {

    // we define the sucess block:
    void (^successBlock)(AFHTTPRequestOperation *operation, id responseObject) = nil;

    // Using NSInvocation, we get access to the concrete block function
    // that has been passed in by the actual test
    // the arguments for the actual method start with 2 (see NSInvocation doc)
    [invocation getArgument:&successBlock atIndex:4];

    // now we invoke the successBlock with some "JSON"...:
    successBlock(nil,
      [NSDictionary dictionaryWithObjectsAndKeys:@"Bom Dia", @"greetings", nil]
    );

}] getPath:[OCMArg any] parameters:nil success:[OCMArg any] failure:[OCMArg any]] ;

// 2) run the actual test
[mockClient getPath:@"projects" parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
  STAssertEqualObjects(
     @"Bom Dia",
     [responseObject objectForKey:@"greetings"],
     @"Some faked JSON"
  );

} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
    NSLog(@"FAIL");
}];

Now this test passes as well, and the responseObject contains the NSDictionary (-> JSON) that we created inside of our expectations… Of course only the success block was invoked, but invoking the failure block is not that much harder.

About these ads

Howdy!

Posted in aerogear

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 33 other followers

%d bloggers like this: