How to stub a call to a method with a reference argument? - objective-c

Consider this:
+(NSDictionary *)getDictionaryFromData:(id)data {
#synchronized(self) {
NSError *error = nil;
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&error];
if (error) {
DLog(#"SERIALIZATION FAILED: %#", error.localizedDescription);
return nil;
}
DLog(#"SUCCESS: %#", dict);
return dict;
}
}
How do I mock getDictionaryFromData to get coverage if error is not nil? Is it possible or do I have to actually mock the JSONObjectWithData method?

For this answer I'm assuming you don't actually want to mock the getDictionaryFromData: method. I assume you want to test its implementation, how it deal with an error case.
You can stub that JSONObjectWithData:options:error: method and return an error in the pass by ref argument; somehow like this:
id serializerMock = [OCMock mockForClass:[NSJSONSerialization class]];
NSError *theError = /* create an error */
[[[serializerMock stub] andReturn:nil] JSONObjectWithData:data
options:NSJSONReadingAllowFragments error:[OCMArg setTo:theError]];
The trick here is obviously the setTo: method.

Here is what worked for me (OCMock 3):
id serializerMock = OCMClassMock(NSJSONSerialization.class);
NSError *theError = [[NSError alloc] initWithDomain:NSCocoaErrorDomain code:NSPropertyListWriteInvalidError userInfo:nil];
OCMStub(ClassMethod([serializerMock dataWithJSONObject:OCMOCK_ANY
options:0
error:[OCMArg setTo:theError]]));
Note: For the line [serializerMock dataWithJSONObject...] Xcode's code completion does not work.

Related

why get data from dataTaskWithURL:completionHandler: to late

i have some misunderstands with completion Handler in
- dataTaskWithURL:completionHandler:.
TableViewController.m
- (IBAction)search:(id)sender {
if ([self.textField.text isEqual: #""]) {
[self textFieldAnimation];
} else {
[self.dataWork takeAndParseDataFromFlickrApiWithTag:self.textField.text];
[self.itemStore fillItemsStore:self.dataWork];
[self.tableView reloadData];
}
}
when i call takeAndParseDataFromFlickrApiWithTag: i want to download some data from Flickr Api and then parse it and make array with JSON objects in dictionaries.
DataWorkWithFlickrApi.m
- (void)takeAndParseDataFromFlickrApiWithTag:(NSString *)tag {
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
NSString *prerareStringForUrl = [NSString stringWithFormat:#"https://api.flickr.com/services/rest/?method=flickr.photos.search&api_key=%#&tags=%#&format=json&nojsoncallback=1", self.apiKey, tag];
self.url = [NSURL URLWithString:prerareStringForUrl];
self.session = [NSURLSession sessionWithConfiguration:config delegate:nil delegateQueue:nil];
[[self.session dataTaskWithURL:self.url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSDictionary *photos = dict[#"photos"];
self.array = photos[#"photo"];
// NSLog(#"%#", self.array);
}] resume];
}
when this method is finished i go to the next [self.itemStore fillItemsStore:self.dataWork]; but at this moment in my array i have 0 objects, and then when i used second time - (IBAction)search: just then my table view showed me a list with objects and i have in array 100 objects and in that time there is uploading a new hundred objects.
So questions is why loading data so late? why I just don't get the data as a method takeAndParseDataFromFlickrApiWithTag: finishes? How to fix it?
sorry for my English
The whole point of a completion handler is that it is called when the task is completed.
You need to trigger processing of the next item in your completion handler and not when -takeAndParseDataFromFlickrApiWithTag: returns.

recursive function get "Variable is not assignable (missing __block type specifier) error"

I want to send AFNetworking requests consequently in a queue. I create a recursive function as below for this aim:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
NSLog(#"Start ...");
[self sentTest:0];
}
- (void) sentTest:(int)i{
if(i >= 10)
{
NSLog(#"Finished");
return;
}
NSLog(#"sending message %d ...", i);
NSMutableDictionary *params = [#{#"param1": #"value1",
#"param2": #"value2",
} mutableCopy];
NSString *webServiceUrl = #"MY_REST_SERVICE_URL";
AFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc] init];
manager.responseSerializer = [AFJSONResponseSerializer serializer];
[manager POST:webServiceUrl parameters:params success:^(NSURLSessionDataTask *task, id responseObject) {
NSLog(#"message sent successful %d", i);
// Now call the method again
[self sentTest:i++];
return;
} failure:^(NSURLSessionDataTask *task, NSError *error) {
NSLog(#"message sent failure %d", i);
return;
}];
}
I get this error:
Variable is not assignable (missing __block type specifier) error"
I know that I need to define block type, but I don't know how to use it in this recursive function.
I had some concerns that manager was being destroyed when the method ends, however it's being retained by the block.
I am not certain your code will work, however the actual error message relates to updating i in the block (and it would need to have the __block attribute applied), however there is no reason to increment i at all, simply pass in i + 1 to the recursed method:
[self sentTest:i + 1];
(you had a missing semicolon in your code, so I am not convinced that is the real code or not).

How do I perform a kiwi unit test, inside a JSON callback?

I am trying to run a kiwi test, it doesn't evaluate the kiwi statement on the inner block. But it will evaluate any test statements outside the block. What do I do?
:
- (void) jsonTest:(void (^)(NSDictionary *model))jsonData{
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager GET:#"http://api.somesite.com" parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
if(jsonData){
jsonData((NSDictionary *)responseObject);
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
jsonData(nil);
}];
}
describe(#"Networking", ^{
it(#"Get Sample JSON", ^{
[[NetworkingUtil alloc] jsonTest:^(NSDictionary *model){
NSString * result = [model objectForKey:#"Host"];
NSLog(#"result :: %#", result);
[[result should] equal: #"host.value.name"];
}];
//kiwi evaluates this test statement though...
[[theValue(41) should] equal:theValue(42)];
}];
You need to use KWCaptureSpy.
NetworkingUtil *jsonT = [NetworkingUtil alloc];
// We tell the spy what argument to capture.
KWCaptureSpy *spy = [jsonT captureArgument:#selector(jsonTest:) atIndex:0];
[jsonT jsonTest:^(NSDictionary *model){
NSString * result = [model objectForKey:#"Host"];
NSLog(#"result :: %#", result);
[[result should] equal: #"host.value.name"];
}];
void (^myTestBlock)(NSDictionary *model) = spy.argument;
myTestBlock(dictionary);
You do have to create a dictionary that you will pass for a test. Its the same for any block even the one inside the jsonTest: method.
When it comes to Kiwi and testing blocks in blocks it gets a bit crazy but the concept is the same. You capture the method that has the completion block you capture the argument that is the block you wish to test and pass it an object it requires.

AFNetwork with ASP.net Web service

I call my webservice using the getPath method like so:
[[AFTwitterAPIClient sharedClient] getPath:#"GetWineCategoryList"
parameters:parameters success:^(AFHTTPRequestOperation *operation, id JSON) {
NSLog(#"JSON = %#",JSON);
NSMutableArray *mutableTweets = [NSMutableArray arrayWithCapacity:[JSON count]];
for (NSDictionary *attributes in mutableTweets) {
Tweet *tweet = [[Tweet alloc] initWithAttributes:attributes];
[mutableTweets addObject:tweet];
}
if (block) {
block([NSArray arrayWithArray:mutableTweets], nil);
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
if (block) {
block([NSArray array], error);
}
}];
It always outputs as NSData. If I convert this NSData to a string I get a JSON String.
JSON = <5b7b2243 61746567 6f72794e 616d6522 3a224f54 48455222 7d2c7b22 43617465 676f7279 4e616d65 223a2252 4544227d 2c7b2243 61746567 6f72794e 616d6522 3a22524f 5345227d 2c7b2243 61746567 6f72794e 616d6522 3a225748 49544522 7d5d>
Why won't it convert to an NSArray?
You can use the NSJSONSerialization Class to convert your NSData object to an NSArray.
NSArray *array = (NSArray*)[NSJSONSerialization JSONObjectWithData:JSON
options:0
error:&error];
This assumes you know that your JSON is of array type and not dictionary type.

How to test a class that makes HTTP request and parse the response data in Obj-C?

I Have a Class that needs to make an HTTP request to a server in order to get some information. For example:
- (NSUInteger)newsCount {
NSHTTPURLResponse *response;
NSError *error;
NSURLRequest *request = ISKBuildRequestWithURL(ISKDesktopURL, ISKGet, cookie, nil, nil);
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
if (!data) {
NSLog(#"The user's(%#) news count could not be obtained:%#", username, [error description]);
return 0;
}
NSString *regExp = #"Usted tiene ([0-9]*) noticias? no leĆ­das?";
NSString *stringData = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
NSArray *match = [stringData captureComponentsMatchedByRegex:regExp];
[stringData release];
if ([match count] < 2)
return 0;
return [[match objectAtIndex:1] intValue];
}
The things is that I'm unit testing (using OCUnit) the hole framework but the problem is that I need to simulate/fake what the NSURLConnection is responding in order to test different scenarios and because I can't relay on the server to test my framework.
So the question is Which is the best ways to do this?
It's always tricky to test methods that call class methods like NSURLConnection sendSynchronousRequest
Here are a couple of options:
a) Use Matt Gallagher's invokeSupersequent macro to intercept the call. Your unit test would contain code like this:
#implementation NSURLConneciton (UnitTests)
+ (NSData *)sendSynchronousRequest:(NSURLRequest *)request returningResponse:(NSURLResponse **)response error:(NSError **)error {
if (someFlagYourTestUsesToInterceptTheCall) {
// return test NSData instance
}
return invokeSupersequent(request, &response, &error);
}
#end
Then you set someFlagYourTestUsesToInterceptTheCall to force it to intercept the call and return your test data.
b) Another alternative is to move that call into its own method in your class under test:
-(NSData *)retrieveNewsCount:(NSURLRequest *)request {
NSHTTPURLResponse *response;
NSError *error;
return [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
}
Then intercept that call in your test case using OCMock:
-(void)testNewsCount {
// instantiate your class
id myObject = ...;
id mock = [OCMockObject partialMockForObject:myObject];
[[[mock stub] andCall:#selector(mockNewsCount:) onObject:self] retrieveNewsCount:[OCMArg any]];
NSUInteger count = [myObject newsCount];
// validate response
...
}
// in the same test class:
-(NSData *)mockNewsCount:(NSURLRequest *)request {
// return your mock data
return mockData;
}
In this case, OCMock's stub:andCall:onObject:someMethod intercepts just this call to your object's method in order to inject some test data at test time.