I have a networking class called: ITunesAlbumDataDownloader
#implementation AlbumDataDownloader
- (void)downloadDataWithURLString:(NSString *)urlString
completionHandler:(void (^)(NSArray *, NSError *))completionHandler
{
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session dataTaskWithURL:[NSURL URLWithString:urlString]
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error)
{
NSArray *albumsArray = [self parseJSONData:data];
completionHandler(albumsArray, error);
}];
[dataTask resume];
}
- (NSArray *)parseJSONData:(NSData *)data {
NSMutableArray *albums = [[NSMutableArray alloc] init];
...
...
// Return the array
return [NSArray arrayWithArray:albums];
}
#end
and i need to create a Unit Test for this which does the following:
The NSURLSession dataTaskWithRequest:completionHandler: response is mocked to contain the fake JSON data i have:
// Expected JSON response
NSData *jsonResponse = [self sampleJSONData];
The returned array from the public method downloadDataWithURLString:completionHandler: response should contain all the albums and nil error.
Other points to bare in mind is that i need to mock NSURLSession with the fake JSON data "jsonResponse" to the downloadDataWithURLString:completionHandler: method WITHOUT invoking an actual network request.
I have tried various different things but i just can not work it out, i think its the combination of faking the request and the blocks which is really confusing me.
Here is two examples of my test method that i tried (i actually tried a lot of other ways also but this is what i have remaining right now):
- (void)testValidJSONResponseGivesAlbumsAndNilError {
// Given a valid JSON response containing albums and an AlbumDataDownloaderTests instance
// Expected JSON response
NSData *jsonResponse = [self sampleJSONDataWithAlbums];
id myMock = [OCMockObject mockForClass:[NSURLSession class]];
[[myMock expect] dataTaskWithRequest:OCMOCK_ANY
completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error)
{
}];
[myMock verify];
}
and
- (void)testValidJSONResponseGivesAlbumsAndNilError {
// Given a valid JSON response containing albums and an AlbumDataDownloaderTests instance
// Expected JSON response
NSData *jsonResponse = [self sampleJSONDataWithAlbums];
id myMock = [OCMockObject mockForClass:[AlbumDataDownloader class]];
[[[myMock stub] andReturn:jsonResponse] downloadDataWithURLString:OCMOCK_ANY
completionHandler:^(NSArray *response, NSError *error)
{
}];
[myMock verify];
}
}
I have a feeling that in both instances I'm probably way off the mark :(
I would really appreciate some help with this.
Thanks.
UPDATE 1:
Here is what i have now come up with but need to know if I'm on the right track or still making a mistake?
id mockSession = [OCMockObject mockForClass:[NSURLSession class]];
id mockDataTask = [OCMockObject mockForClass:[NSURLSessionDataTask class]];
[[mockSession stub] dataTaskWithRequest:OCMOCK_ANY
completionHandler:^(NSData _Nullable data, NSURLResponse Nullable response, NSError * Nullable error)
{
NSLog(#"Response: %#", response);
}];
[[mockDataTask stub] andDo:^(NSInvocation *invocation)
{
NSLog(#"invocation: %#", invocation);
}];
The trick with blocks is you need the test to call the block, with whatever arguments the test wants.
In OCMock, this can be done like this:
OCMStub([mock someMethodWithBlock:([OCMArg invokeBlockWithArgs:#"First arg", nil])]);
This is convenient. But…
The downside is that the block will be invoked immediately when someMethodWithBlock: is called. This often doesn't reflect the timing of production code.
If you want to defer calling the block until after the invoking method completes, then capture it. In OCMock, this can be done like this:
__block void (^capturedBlock)(id arg1);
OCMStub([mock someMethodWithBlock:[OCMArg checkWithBlock:^BOOL(id obj) {
capturedBlock = obj;
return YES;
}]]);
// ...Invoke the method that calls someMethodWithBlock:, then...
capturedBlock(#"First arg"); // Call the block with whatever you need
I prefer to use OCHamcrest's HCArgumentCaptor. OCMock supports OCHamcrest matchers, so I believe this should work:
HCArgumentCaptor *argument = [[HCArgumentCaptor alloc] init];
OCMStub([mock someMethodWithBlock:argument]);
// ...Invoke the method that calls someMethodWithBlock:, then...
void (^capturedBlock)(id arg1) = argument.value; // Cast generic captured argument to specific type
capturedBlock(#"First arg"); // Call the block with whatever you need
Related
I'm making a URLRequest that sends attempted login information and waits for a response from the web service to find out if the user can/cannot log in.
The way I was hoping to do this was by having the user type in his username & password into two text fields and then press a button, which would call the function below. This function would start an NSURLSessionDataTask and construct a struct with the boolean success/failure of the login and an NSString with the corresponding error message (if any).
The problem is that my function returns the struct before my NSURLSessionDataTask's completion block has finished executing. Is there a way for me to force my program to wait until this task either times out or completes? Alternatively, can I push execution of the completion block onto the main thread & before the function returns?
Thanks! Please let me know if there are any clarifications I need to make!
(Also, I have seen some similar questions circulating around StackOverflow that mention GCD. Is this an overkill solution? None of those questions seem to be talking about quite the same thing, or do so on a level that is higher than my current understanding. I am still very new to Objective-C)
- (struct RequestReport) sendLoginRequest: (NSString*) username withPassword: (NSString *) password
... (creating the request & setting HTTP body)
NSURLSessionDataTask *dataTask = [self.session dataTaskWithRequest:request completionHandler: ^(NSData *data, NSURLResponse *response, NSError *error){
NSDictionary *jsonObject = [NSJSONSerialization JSONObjectWithData: data options:0 error:nil];
success = (BOOL)jsonObject[#"success"];
statusText = (NSString *) jsonObject[#"errors"];
}];
[dataTask resume];
struct RequestReport rr;
rr.status = statusText;
rr.success = success;
return rr;
Your method should look like this:
- (void) sendLoginRequest:(NSString*) username withPassword:(NSString *) password callback:(void (^)(NSError *error, BOOL success))callback
{
NSURLSessionDataTask *dataTask = [self.session dataTaskWithRequest:request completionHandler: ^(NSData *data, NSURLResponse *response, NSError *error){
if (error) {
// Handle error
}
else {
callback(error, YES);
}
}];
[dataTask resume];
}
Call this method like so:
[self sendLoginRequest:#"myUsername" password:#"password" callback:^(NSString *error, BOOL success) {
if (success) {
NSLog(#"My response back from the server after an unknown amount of time";
}
}
See Apple's Programming with Objective-C for more reading on blocks and fuckingblocksyntax.com for how to declare blocks.
I'm using AFNetworking 2.0 to access a web api (although this would apply to NSURLSession as well), and currently I have a bunch of code that looks like this:
[self.rottenTomatoesManager GET:#"movies.json" parameters:#{#"q" : searchString, #"apikey" : [self.rottenTomatoesManager apiKey]}
success:^(NSURLSessionDataTask *task, id responseObject) {
NSHTTPURLResponse *response = (NSHTTPURLResponse *)task.response;
if(response.statusCode == 200){
NSDictionary *responseData = responseObject;
self.searchResults = responseData[#"movies"];
[self.searchDisplayController.searchResultsTableView reloadData];
[self.tableView reloadData];
}
}
failure:^(NSURLSessionDataTask *task, NSError *error) {
NSLog(#"Error loading movies %#", error.localizedDescription);
}];
I'd like to take that functionality and wrap it in a convenience method that looks something like this
NSArray *results = [self.rottenTomatoesManager searchMoviesWithTitle:#"The avengers"]
to clean up the ViewController code and to make most of the code framework agnostic.
What is the best way to do this so that I'm not turning a nice asynchronous blocks based API into a synchronous API?
Callback blocks are great for this.
[self loadSomethingWithCallback:^(NSArray *results) {
NSLog(#"%#", results);
}];
I’m sorry if this question is too basic, but I can’t seem to find a an answer online.
I want to fetch the JSON result and have them returned with the class method below. But as you can see, by fetching the JSON in the block method, I don’t have a way to return them as result.
What is the correct way to to return them as NSDictionary from inside block method, or is there any other way to simplify this?
+ (NSDictionary *) fetchtPostsCount:(NSString *) count
page: (NSString *) page {
NSDictionary *requestParameter = [[NSDictionary alloc] initWithObjectsAndKeys:count, #"count", page, #"page", nil];
[[self sharedClient] GET:#"get_recent_posts"
parameters:requestParameter success:^(NSURLSessionDataTask *task, id responseObject) {
NSLog(#"%#", [responseObject objectForKey:#"posts"]);
} failure:^(NSURLSessionDataTask *task, NSError *error) {
NSLog(#"%#", error);
}];
return nil;
}
AFNetworking executes requests on different thread, and calls the success or failure block when its done. Conceptually, you can imagine that your fetchPostsCount method will have already completed and returned its value by the time request is finished.
You almost certainly want it to work that way. Running the request on another thread and NOT waiting for it, allows your main UI thread to continue processing events and rendering screen updates. You don't want to get in the way of those things, or the user (and iOS) will get unhappy.
However, if you insist on waiting for the request to complete before returning, you could set a flag to monitor the status of the request, and then wait on that flag until the request is complete:
BOOL requestComplete = NO;
id requestResponseObject = nil;
[[self sharedClient] GET:#"get_recent_posts"
parameters:requestParameter success:^(NSURLSessionDataTask *task, id responseObject) {
requestResponseObject = responseObject;
requestComplete = YES;
NSLog(#"%#", [responseObject objectForKey:#"posts"]);
} failure:^(NSURLSessionDataTask *task, NSError *error) {
requestComplete = YES;
NSLog(#"%#", error);
}];
while (!requestComplete)
{
// Tie up the thread, doing nothing...
}
// Proceed
I am a little bit confused about objective c programming with blocks.
for example Here is a method:
in the .h
- (void)downloadDataWithURLString:(NSString *)urlString
completionHandler:(void(^) (NSArray * response, NSError *error))completionHandler;
in the .m:
- (void)downloadedDataURLString:(NSString *)urlString
completionHandler:(void (^)(NSArray *, NSError *))completionHandler {
// some things get done here. But what!?
}
My main questions is.... how do I implement this completion handler? What variables would be returned with the array and error? it is one area for the code but how do I tell it what to do when it is completed?
It's up to the caller to supply code to be run by the method (the body of the block). It's up to the implementor to invoke that code.
To start with a simple example, say the caller just wanted you to form an array with the urlString and call back, then you would do this:
- (void)downloadedDataURLString:(NSString *)urlString
completionHandler:(void (^)(NSArray *, NSError *))completionHandler {
NSArray *callBackWithThis = #[urlString, #"Look ma, no hands"];
completionHandler(callBackWithThis, nil);
}
The caller would do this:
- (void)someMethodInTheSameClass {
// make an array
[self downloadedDataURLString:#"put me in an array"
completionHandler:^(NSArray *array, NSError *error) {
NSLog(#"called back with %#", array);
}];
}
The caller will log a two item array with #"put me in an array" and #"Look ma, no hands". In a more realistic example, say somebody asked you to call them back when you're finished downloading something:
- (void)downloadedDataURLString:(NSString *)urlString
completionHandler:(void (^)(NSArray *, NSError *))completionHandler {
// imagine your caller wants you to do a GET from a web api
// stripped down, that would look like this
// build a request
NSURL *url = [NSURL URLWithString:urlString];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
// run it asynch
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if (!error) {
// imagine that the api answers a JSON array. parse it
NSError *parseError;
id parse = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&parseError];
// here's the part you care about: the completionHandler can be called like a function. the code the caller supplies will be run
if (!parseError) {
completionHandler(parse, nil);
} else {
NSLog(#"json parse error, error is %#", parseError);
completionHandler(nil, parseError);
}
} else {
NSLog(#"error making request %#", error);
completionHandler(nil, error);
}
}];
// remember, this launches the request and returns right away
// you are calling the block later, after the request has finished
}
While I can't be entirely sure without seeing any more details about the method, or its exact implementation I suspect this: this methods creates a new background thread, retrieves the data from the server and converts the JSON/XML to an NSArray response. If an error occurred, the error object contains a pointer to the NSError. After doing that, the completion handler is called on the main thread. The completion handler is the block in which you can specify which code should be executed after attempting to retrieve the data.
Here is some sample code on how to call this method to get you started:
[self downloadDataWithURLString:#"http://www.google.com"
completionHandler:^(NSArray *response, NSError *error) {
if (! error) {
// Do something awesome with the 'response' array
} else {
NSLog(#"An error occured while downloading data: %#", error);
}
}];
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.