How can I unit test my NSURLConnection delegate?
I made a ConnectionDelegate class which conforms to different protocols to serve data from the web to different ViewControllers. Before I get too far I want to start writing my unit tests.
But I don't know how to test them as a unit without the internet connection. I would like also what I should do to treat the asynchronous callbacks.
This is similar to Jon's response, couldn't fit it into a comment, though. The first step is to make sure you are not creating a real connection. The easiest way to achieve this is to pull the creation of the connection into a factory method and then substitute the factory method in your test. With OCMock's partial mock support this could look like this.
In your real class:
- (NSURLConnection *)newAsynchronousRequest:(NSURLRequest *)request
{
return [[NSURLConnection alloc] initWithRequest:request delegate:self];
}
In your test:
id objectUnderTest = /* create your object */
id partialMock = [OCMockObject partialMockForObject:objectUnderTest];
NSURLConnection *dummyUrlConnection = [[NSURLConnection alloc]
initWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:#"file:foo"]]
delegate:nil startImmediately:NO];
[[[partialMock stub] andReturn:dummyUrlConnection] newAsynchronousRequest:[OCMArg any]];
Now, when your object under test tries to create the URL connection it actually gets the dummy connection created in the test. The dummy connection doesn't have to be valid, because we're not starting it and it never gets used. If your code does use the connection you could return another mock, one that mocks NSURLConnection.
The second step is to invoke the method on your object that triggers the creation of the NSURLConnection:
[objectUnderTest doRequest];
Because the object under test is not using the real connection we can now call the delegate methods from the test. For the NSURLResponse we're using another mock, the response data is created from a string that's defined somewhere else in the test:
int statusCode = 200;
id responseMock = [OCMockObject mockForClass:[NSHTTPURLResponse class]];
[[[responseMock stub] andReturnValue:OCMOCK_VALUE(statusCode)] statusCode];
[objectUnderTest connection:dummyUrlConnection didReceiveResponse:responseMock];
NSData *responseData = [RESPONSE_TEXT dataUsingEncoding:NSASCIIStringEncoding];
[objectUnderTest connection:dummyUrlConnection didReceiveData:responseData];
[objectUnderTest connectionDidFinishLoading:dummyUrlConnection];
That's it. You've effectively faked all the interactions the object under test has with the connection, and now you can check whether it is in the state it should be in.
If you want to see some "real" code, have a look at the tests for a class from the CCMenu project that uses NSURLConnections. This is a little bit confusing because the class that's tested is named connection, too.
http://ccmenu.svn.sourceforge.net/viewvc/ccmenu/trunk/CCMenuTests/Classes/CCMConnectionTest.m?revision=129&view=markup
EDIT (2-18-2014): I just stumbled across this article with a more elegant solution.
http://www.infinite-loop.dk/blog/2011/04/unittesting-asynchronous-network-access/
Essentially, you have the following method:
- (BOOL)waitForCompletion:(NSTimeInterval)timeoutSecs {
NSDate *timeoutDate = [NSDate dateWithTimeIntervalSinceNow:timeoutSecs];
do {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:timeoutDate];
if([timeoutDate timeIntervalSinceNow] < 0.0)
break;
} while (!done);
return done;
}
At the end of your test method, you make sure things haven't timed out:
STAssertTrue([self waitForCompletion:5.0], #"Timeout");
Basic format:
- (void)testAsync
{
// 1. Call method which executes something asynchronously
[obj doAsyncOnSuccess:^(id result) {
STAssertNotNil(result);
done = YES;
}
onError:^(NSError *error) [
STFail();
done = YES;
}
// 2. Determine timeout
STAssertTrue([self waitForCompletion:5.0], #"Timeout");
}
==============
I'm late to the party, but I came across a very simple solution. (Many thanks to http://www.cocoabuilder.com/archive/xcode/247124-asynchronous-unit-testing.html)
.h file:
#property (nonatomic) BOOL isDone;
.m file:
- (void)testAsynchronousMethod
{
// 1. call method which executes something asynchronously.
// 2. let the run loop do its thing and wait until self.isDone == YES
self.isDone = NO;
NSDate *untilDate;
while (!self.isDone)
{
untilDate = [NSDate dateWithTimeIntervalSinceNow:1.0]
[[NSRunLoop currentRunLoop] runUntilDate:untilDate];
NSLog(#"Polling...");
}
// 3. test what you want to test
}
isDone is set to YES in the thread that the asynchronous method is executing.
So in this case, I created and started the NSURLConnection at step 1 and made the delegate of it this test class. In
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
I set self.isDone = YES;. We break out of the while loop and the test is executed. Done.
I avoid networking in unit tests. Instead:
I isolate NSURLConnection within a method.
I create a testing subclass, overriding that method to remove all traces of NSURLConnection.
I write one test to ensure that the method in question will get invoked when I want. Then I know it'll fire off an NSURLConnection in real life.
Then I concentrate on the more interesting part: Synthesize mock NSURLResponses with various characteristics, and pass them to the NSURLConnectionDelegate methods.
My favorite way of doing this is to subclass NSURLProtocol and have it respond to all http requests - or other protocols for that matter. You then register the test protocol in your -setup method and unregisters it in your -tearDown method.
You can then have this test protocol serve some well known data back to your code so you can validate it in your unit tests.
I have written a few blog articles about this subject. The most relevant for your problem would probably be Using NSURLProtocol for Injecting Test Data and Unit Testing Asynchronous Network Access.
You may also want to take a look my ILCannedURLProtocol which is described in the previous articles. The source is available at Github.
you might want to give this a chance:
https://github.com/marianoabdala/ZRYAsyncTestCase
Here's some sample code of a NSURLConnection being unit tested:
https://github.com/marianoabdala/ZRYAsyncTestCase/blob/12a84c7f1af1a861f76c7825aef6d9d6c53fd1ca/SampleProject/SampleProjectTests/SampleProjectTests.m#L33-L56
Here's what I do:
Get XAMPP Control Panel
http://www.apachefriends.org/en/xampp.html
Start the apache server
In your ~/Sites folder, put a test file (whatever data you want, we'll call it my file.test).
Start your delegate using the URL http://localhost/~username/myfile.test
Stop the apache server when not using it.
Related
hello i have done this QCM in an iOS test. my choice is B. and it's wrong. i hope someone could help me understand. Thank you in advance.
Here is the question:
NSURLConnection instance created and started inside the implementation of "start" method of the concurrent NSOperation.
What should be changed to make NSURLConnection's delegate protocol method call in this NSOperation instance?
-(void)start{
...
_connection = [[NSURLConnection alloc] initWithRequest: request delegate:self startImmediately:NO];
[_connection scheduleInRunloop: [NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
[_connection start];
... }
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
NSLog(#"Called"); }
there are three answers provide:
A: Set startImmediately to YES
B: Wrap this code to dispatch_async(dispatch_get_current_queue(),^{});
C: Nothing, didReceiveResponse will be called.
No change is necessary to this code sample (option C).
Setting startImmediately to YES (option A) would merely start the connection prior to scheduling it on the appropriate run loop, which would cause problems.
You could, theoretically dispatch the creation of the connection to the main thread (option B), thus eliminating the need to manually schedule it on the main run loop. But your code sample (manually scheduling it on the main run loop) accomplishes the same thing and is the more common pattern.
Bottom line, you existing code sample is fine.
I am trying to start a second NSURLConnection after starting the first one. My first one works perfectly: the appropriate delegates are all called, and everything executes as planned. However, after the first one finishes, I create a second one, and nothing happens (the delegate is never called). What could be happening? I know I can't reuse the same NSURLConnection, but I reinitialise it before using it again, so it should be a completely new connection.
Here's my code for starting (both) connections. It's the same instance variable, but it's reinitialised. Also note that the second one is not started until the first one has finished running completely.
if (connection) {
[connection cancel];
}
currentResponse = nil;
error = nil;
connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
if (!connection) {
NSLog(#"Connection could not be initialized.");
[self connectionFinished];
} else {
[connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[connection start];
}
You haven't shared how you're running it "on a different thread", but the typical problem is if you are using dispatch queues or operation queues, your connection is running asynchronously, itself, and therefore the dispatched operation is completing and getting released and you're losing your connection.
A couple of possible solutions:
You can perform your network operations synchronously in this background operation of yours (this is the only time you should do synchronous network operations). This is the simplest solution, though you haven't explained what you're doing with your NSURLConnection, so this technique may or may not work for you. But if you're just trying to download something from a URL, you can do:
NSData *data = [NSData dataWithContentsOfURL:url
options:0
error:&error];
This approach doesn't work if you're doing any a little more complicated that requires the NSURLConnectionDataDelegate methods, such challenge-response authentication or if you are streaming using didReceiveData to reduce your app's memory footprint or for performances reasons, etc. But if you're just trying to download data from a remote server (e.g. retrieving an image, an XML/JSON feed, etc.), this is easiest.
In a similar vein (i.e. you don't need the NSURLConnectionDataDelegate methods), but you're you're creating some rich NSURLRequest for your connection, then you can use either sendAsynchronousRequest or sendSynchronousRequest.
If you need the NSURLConnectionDataDelegate calls, you can use the setDelegateQueue (designating a NSOperationQueue) or scheduleInRunLoop (designating a NSRunLoop), and it will automatically dispatch the connection updates to the appropriate queue/runloop. Just make sure to initWithRequest using the startImmediately option of NO, set the delegate queue or runloop, and then start the connection. With this technique, you preserve the full richness of NSURLConnectionDataDelegate if you absolutely need it.
Alternatively, if you're not using operation queues, you could also keep your background operation alive until the connection is done. This approach gives you synchronous behavior in you background operation (keeping your connection alive), while preserving the NSURLConnectionDataDelegate methods. This technique is demonstrated by Apple's XMLPerformance Sample (see the downloadAndParse method of CocoaXMLParser.m and LibXMLParser.m), where they initiate the NSURLConnection and then use the following construct to keep the background operation alive until the NSURLConnectionDataDelegate methods end up setting the done instance variable:
do {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
beforeDate:[NSDate distantFuture]];
} while (!done);
I confess that I find this final approach vaguely dissatisfying and would lean towards the other alternatives, depending upon what flexibility and functionality you need from your NSURLConnection. For us to provide more meaningful counsel, you just need to provide more information about (a) the sort of work you're doing in your NSURLConnectionDataDelegate methods; and (b) which technology you're using to run your code in the background.
For a few more options, also feel free to see GCD and async NSURLConnection.
I have an NSOperationQueue that handles importing data from a web server on a loop. It accomplishes this with the following design.
NSURLConnect is wrapped in an NSOperation and added to the Queue
On successful completion of the download (using a block), the data from the request is wrapped in another NSOperation that adds the relevant data to Core Data. This operation is added to the queue.
On successful completion (using another block), (and after a specified delay) I call the method that started it all and return to step 1. Thus, i make another server call x seconds later.
This works great. I'm able to get data from the server and handle everything on the background. And because these are just NSOperations I'm able to put everything in the background, and perform multiple requests at a time. This works really well.
The ONLY problem that I currently have is that I'm unable to successfully cancel the operations once they are going.
I've tried something like the following :
- (void)flushQueue
{
self.isFlushingQueue = YES;
[self.operationQueue cancelAllOperations];
[self.operationQueue waitUntilAllOperationsAreFinished];
self.isFlushingQueue = NO;
NSLog(#"successfully flushed Queue");
}
where self.isFlushingQueue is a BOOL that I use to check before adding any new operations to the queue. This seems like it should work, but in fact it does not. Any ideas on stopping my Frankenstein creation?
Edit (Solved problem, but from a different perspective)
I'm still baffled about why exactly I was unable to cancel these operations (i'd be happy to keep trying possible solutions), but I had a moment of insight on how to solve this problem in a slightly different way. Instead of dealing at all with canceling operations, and waiting til queue is finished, I decided to just have a data structure (NSMutableDictionary) that had a list of all active connections. Something like this :
self.activeConnections = [NSMutableDictionary dictionaryWithDictionary:#{
#"UpdateContacts": #YES,
#"UpdateGroups" : #YES}];
And then before I add any operation to the queue, I simply ask if that particular call is On or Off. I've tested this, and I successfully have finite control over each individual server request that I want to be looping. To turn everything off I can just set all connections to #NO.
There are a couple downsides to this solution (Have to manually manage an additional data structure, and every operation has to start again to see if it's on or off before it terminates).
Edit -- In pursuit of a more accurate solution
I stripped out all code that isn't relevant (notice there is no error handling). I posted two methods. The first is an example of how the request NSOperation is created, and the second is the convenience method for generating the completion block.
Note the completion block generator is called by dozens of different requests similar to the first method.
- (void)updateContactsWithOptions:(NSDictionary*)options
{
//Hard coded for ease of understanding
NSString *contactsURL = #"api/url";
NSDictionary *params = #{#"sortBy" : #"LastName"};
NSMutableURLRequest *request = [self createRequestUsingURLString:contactsURL andParameters:params];
ConnectionCompleteBlock processBlock = [self blockForImportingDataToEntity:#"Contact"
usingSelector:#selector(updateContactsWithOptions:)
withOptions:options andParsingSelector:#selector(requestUsesRowsFromData:)];
BBYConnectionOperation *op = [[BBYConnectionOperation alloc] initWithURLRequest:request
andDelegate:self
andCompletionBlock:processBlock];
//This used to check using self.isFlushingQueue
if ([[self.activeConnections objectForKey:#"UpdateContacts"] isEqualToNumber:#YES]){
[self.operationQueue addOperation:op];
}
}
- (ConnectionCompleteBlock) blockForImportingDataToEntity:(NSString*)entityName usingSelector:(SEL)loopSelector withOptions:(NSDictionary*)options andParsingSelector:(SEL)parseSelector
{
return ^(BOOL success, NSData *connectionData, NSError *error){
//Pull out variables from options
BOOL doesLoop = [[options valueForKey:#"doesLoop"] boolValue];
NSTimeInterval timeInterval = [[options valueForKey:#"interval"] integerValue];
//Data processed before importing to core data
NSData *dataToImport = [self performSelector:parseSelector withObject:connectionData];
BBYImportToCoreDataOperation *importOperation = [[BBYImportToCoreDataOperation alloc] initWithData:dataToImport
andContext:self.managedObjectContext
andNameOfEntityToImport:entityName];
[importOperation setCompletionBlock:^ (BOOL success, NSError *error){
if(success){
NSLog(#"Import %#s was successful",entityName);
if(doesLoop == YES){
dispatch_async(dispatch_get_main_queue(), ^{
[self performSelector:loopSelector withObject:options afterDelay:timeInterval];
});
}
}
}];
[self.operationQueue addOperation:importOperation];
};
}
Cancellation of an NSOperation is just a request, a flag that is set in NSOperation. It's up to your NSOperation subclass to actually action that request and cancel it's work. You then need to ensure you have set the correct flags for isExecuting and isFinished etc. You will also need to do this in a KVO compliant manner. Only once these flags are set is the operation finished.
There is an example in the documentation Concurrency Programming Guide -> Configuring Operations for Concurrent Execution. Although I understand that this example may not correctly account for all multi-threaded edge cases. Another more complex example is provided in the sample code LinkedImageFetcher : QRunLoopOperation
If you think you are responding to the cancellation request correctly then you really need to post your NSOperation subclass code to examine the problem any further.
Instead of using your own flag for when it is ok to add more operations, you could try the
- (void)setSuspended:(BOOL)suspend
method on NSOperationQueue? And before adding a new operation, check if the queue is suspended with isSuspended?
I'm looping through a list of dates and making a request to a web server for each date in the list.
I would like each date to be processed completely before the subsequent request is sent to the server. To do this, I have set up a serial dispatch queue using GCD. Each time through the date loop, a block is added to the queue.
The problem I am having is that my NSURLConnection is set up using the standard asynchronous call. This results in requests not blocking any subsequent requests. They are thus overrunning each other.
My question: Is this a case where it would make sense for me to use the synchronous NSURLConnection (within the dispatch queue) or is there some other way to make it work using the standard asynchronous call?
There are number of ways to do this. Whatever method you choose, starting the connection needs to be tied to completion of your processing task.
In each block you add to your serial queue, use a synchronous request. This is probably the quickest solution given your current implementation as long as you're ok with the limited error handling of a synchronous request.
Don't use a serial queue. Start the first asynchronous connection and process the response. When processing is complete start the next asynchronous connection. Rinse and repeat.
I think that using the synchronous NSURLConnection API is a fine idea. You have a few other options. One would be to write a wrapper object around NSURLConnection that used the asynchronous NSURLConnection APIs, so you get the nice information that the asynchronous API callbacks provide, including download progress, you can easily continue to update your UI while the request is happening, but which presents its own synchronous method for doing whatever it is you need to do. Essentially, something like:
#implementation MyURLConnectionWrapper
- (BOOL)sendRequestWithError:(NSError **)error
{
error = error ? error : &(NSError *){ nil };
self.finishedLoading = NO;
self.connectionError = nil;
self.urlConnection = [][NSURLConnection alloc] init...]
while (!self.finishedLoading)
{
[[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode beforeDate: [NSDate distantFuture]];
}
if (self.connectionError != nil)
{
*error = self.connectionError;
return NO;
}
return YES;
}
#end
(This is all typed off the top of my head, and is heavily abbreviated, but should give you the basic idea.)
You could also do something like fire off each request in the completion delegate method for the previous one, forgoing the use of a serial dispatch queue altogether:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection;
{
[self sendNextRequest];
}
Either way, you need to think about how to handle connection errors appropriately. I've used both approaches in different places with good success.
I'm testing real web service calls with OCMock.
Right now I'm doing something like:
- (void)testWebservice
{
id mydelegatemock = [OCMockObject mockForProtocol:#protocol(MySUTDelegate)];
[[mydelegatemock expect] someMethod:[OCMArg any]];
[SUT sutWithDelegate:mydelegatemock];
// we need to wait for real result
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:2.0]];
[(OCMockObject*)mydelegatemock verify];
}
It works fine, but it implies that every such test will take 2 seconds.
Is there a way I can set a timeout of e.g. 2 seconds, and let a call to someMethod of mydelegatemock immediately verify and complete the test case?
I do this using a handy utility function I found at this link:
#import <Foundation/Foundation.h>
#import <OCMock/OCMock.h>
#interface TestUtils : NSObject
+ (void)waitForVerifiedMock:(OCMockObject *)mock delay:(NSTimeInterval)delay;
#end
And the implementation:
#import "TestUtils.h"
#implementation TestUtils
+ (void)waitForVerifiedMock:(OCMockObject *)inMock delay:(NSTimeInterval)inDelay
{
NSTimeInterval i = 0;
while (i < inDelay)
{
#try
{
[inMock verify];
return;
}
#catch (NSException *e) {}
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.5]];
i+=0.5;
}
[inMock verify];
}
#end
This allows me to to wait up to a maximum delay (in seconds) without waiting the full amount each time.
I would separate the functional testing of your web services (if you need to do that at all) from the unit testing of your class that processes the web service result.
To unit test, you should mock the web service call, providing a mock result. Then your test would verify that, for that well-defined result, your class behaves accordingly.
If you also want to do functional testing of your web service (say that it returns a specific response given some request), you don't need to mock anything--just call the service and make assertions on the result.
By separating out your tests, you have finer control over the test runs. For example, you could run your fast-running unit tests every time you change code, but run your slow-running functional tests nightly, on a dedicated server, or as needed. And when a test breaks, you'll know whether it's your code or something wrong with the web service.
You could also switch to GHUnit, like a fellow member suggests in the answer to this related question:
SenTestingKit in Xcode 4: Asynchronous testing?
You can find GHUnit here
https://github.com/gabriel/gh-unit