Kiwi - Expected subject to be nil, got KWAsyncVerifier - objective-c

I'm using Kiwi to write the tests for my app.
I wrote tests to test against my API. I was guided by this example in the documentation for testing asynchronous calls:
https://github.com/allending/Kiwi/wiki/Asynchronous-Testing
My tests are long, so I made a simplified version of my issue:
describe(#"My Class Name", ^{
context(#"populate", ^{
it(#"download the content", ^{
__block NSString *testResponseObject = nil;
__block NSError *testError = nil;
MyClient *apiClient = [MyClient sharedClient];
NSMutableURLRequest *request = [apiClient requestWithMethod:#"DELETE" path:#"my/path" parameters:nil];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
testResponseObject = [[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
testError = error;
}];
[apiClient enqueueHTTPRequestOperation:operation];
[[expectFutureValue(testResponseObject) shouldEventuallyBeforeTimingOutAfter(100)] equal:#"Expected Content"];
[[expectFutureValue(testError) shouldEventuallyBeforeTimingOutAfter(100)] shouldBeNil];
});
});
});
The thing is that if everything works as expected & the operation succeeds the failure block never gets called & instead of nil for NSError I get KWAsyncVerifier.
I'm guessing that's because Kiwi waits for the block where testError is referenced to be executed which never happens & that's why I have KWAsyncVerifier stuck into testError instead of nil.
Is there any alternative how to test this out?

My first recommendation is that you should not test your libraries. From what I read in your example, you are basically checking that AFHTTPRequestOperation is working as documented, but that’s not your responsability to test. You should test that you invoke AFNetworking correctly, and that given an responseObject or an error, your code behaves as you expect.
Anyway, about what you are seeing, you have two “shoulds” in the same line: shouldEventually and shouldBeNil; they use to have beNil matcher, which is unavailable in 2.1, and I think they are bringing back. You can find the discussion in https://github.com/allending/Kiwi/issues/293
Maybe you can try the following to make sure that the failure branch is not taken:
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
testResponseObject = [[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
// This will fail if we ever reach this branch
[error shouldBeNil];
}];
[apiClient enqueueHTTPRequestOperation:operation];
[[expectFutureValue(testResponseObject) shouldEventuallyBeforeTimingOutAfter(100)] equal:#"Expected Content"];
The shouldEventuallyBeforeTimingOutAfter will keep the test case “alive” waiting to check the response, but if you ever go through the failure branch, the other expectation will fail (and also the one in response will fail after 100 seconds). Hope it helps.

Related

Wait for AFNetworking completion block before continuing (i.e. Synchronous)

I have a use case for AFNetworking to behave synchronously (details below). How can I achieve this?
Here is my code snippet, which I've simplified as much as possible.
I would like to return the success response, but I only ever get nil (because the function returns before the block is called).
- (id)sendForUrl:(NSURL *)url {
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
__block id response;
[manager GET:url.absoluteString parameters:nil success: ^(AFHTTPRequestOperation *operation, id responseObject) {
response = responseObject;
NSLog(#"JSON: %#", responseObject);
} failure: ^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
return response;
}
Details
So the reason I need this to behave synchronously is because I am building a pod that will bootstrap an application at start up. The boot strapping hits a services and saves a bunch of values locally. These values are then used for the current session and not changed. If the values change, the user will get a strange experience, so its important I avoid this.
If the service is down, that's okay. We'll use default values or looks for some saved values from a previous session, but whatever happens, we don't want the experience for the user to change in the session.
(This is an engine for A/B testing and experimentation - if that helps you 'get' the use case).
Ignoring the fact that we generally do not want to make synchronous network requests, the traditional solution is to use semaphores:
- (id)sendForUrl:(NSURL *)url {
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.completionQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block id response;
[manager GET:url.absoluteString parameters:nil success: ^(AFHTTPRequestOperation *operation, id responseObject) {
response = responseObject;
NSLog(#"JSON: %#", responseObject);
dispatch_semaphore_signal(semaphore);
} failure: ^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
return response;
}
There are two issues here:
I know you said you didn't want "to see some complex dispatch style things", but with due deference to others, a semaphore is better than a while loop that is spinning, polling until some value changes.
Note, assuming you're calling this method from the main thread, you must set the completionQueue to be some other, background queue. By default, the AFHTTPRequestOperationManager will use the main queue for those completion blocks. And if you block that thread waiting until the completion blocks to run on that same thread, you'll deadlock (i.e. your app will freeze).
For the sake of completeness, I'll point out that invariably, when someone asks "how to I make some asynchronous method behave synchronously", the correct answer is, "you don't". Instead, you generally adopt asynchronous patterns, for example changing the method to take a block parameter:
- (void)sendForUrl:(NSURL *)url completionHandler:(void (^)(id responseObject, NSError *error))completionHandler {
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.completionQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
[manager GET:url.absoluteString parameters:nil success: ^(AFHTTPRequestOperation *operation, id responseObject) {
if (completionHandler) {
completionHandler(responseObject, nil);
}
} failure: ^(AFHTTPRequestOperation *operation, NSError *error) {
if (completionHandler) {
completionHandler(nil, error);
}
}];
}
You'd then call it like so, providing a completion handler block:
[object sendForURL:url completionHandler:^(id responseObject, NSError *error) {
// use responseObject and/or error here
}];
// don't use them here
It sounds like there's a part of your app that can't run until a starting request is done. But there's also a part that can run (like the part that starts the request). Give that part a UI that tells the user that we're busy getting ready. No blocking the UI.
But if you must, and if AFNetworking doesn't provide a blocking version of a request (kudos to them for that), then you could always block the old fashioned way...
- (void)pleaseDontUseThisIdea {
__block BOOL thePopeIsCatholic = YES;
[manager GET: ....^{
// ...
thePopeIsCatholic = NO;
}];
while (thePopeIsCatholic) {}
}

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.

Dealing with AFNetworking2.0 asynchronous HTTP request

I am very new to the concept of asynchronous programming, but I get the general gist of it (things get run in the backround).
The issue I'm having is I have a method which utilizes AFNetworking 2.0 to make an HTTP post request, and it works for the most part.
However, I can't figure out how to get the method to actually return the value received from the response as the method returns and THEN gets the value from the response.
-(int) registerUser
{
self.responseValue = 000; //Notice I set this to 000 at start of method
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.requestSerializer = [AFJSONRequestSerializer serializer];
NSDictionary *parameters = #{ #"Username": #"SomeUsername" };
[manager POST:#"http://XXX/register"
parameters:parameters
success:^(AFHTTPRequestOperation *operation, id responseObject)
{
NSLog(#"JSON: %#", responseObject);
NSError *err = nil;
self.responseValue = [[responseObject objectForKey:#"res"] intValue];
//Note: This^ value returns 99 and NSLogs confirm this
}
failure:^(AFHTTPRequestOperation *operation, NSError *err)
{
NSLog(#"Error: %#", err);
}];
return self.responseValue; //This will return 000 and never 99!
}
Whats the 'proper' way to handle this situation? I've heard whispers of using a 'callback', but I don't really understand how to implement that in this situation.
Any guidance or help would be awesome, cheers!
The issue is that the POST runs asynchronously, as you point out, so you are hitting the return line well before the responseValue property is actually set, because that success block runs later. Put breakpoints/NSLog statements in there, and you'll see you're hitting the return line first.
You generally do not return values from an asynchronous methods, but rather you adopt the completion block pattern. For example:
- (void)registerUserWithCompletion:(void (^)(int responseValue, NSError *error))completion
{
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.requestSerializer = [AFJSONRequestSerializer serializer];
NSDictionary *parameters = #{ #"Username": #"SomeUsername" };
[manager POST:#"http://XXX/register"
parameters:parameters
success:^(AFHTTPRequestOperation *operation, id responseObject)
{
NSLog(#"JSON: %#", responseObject);
int responseValue = [[responseObject objectForKey:#"res"] intValue];
if (completion)
completion(responseValue, nil);
}
failure:^(AFHTTPRequestOperation *operation, NSError *err)
{
NSLog(#"Error: %#", err);
if (completion)
completion(-1, err); // I don't know what you want to return if it failed, but handle it appropriately
}];
}
And then, you could use it as follows:
[self registerUserWithCompletion:^(int responseValue, NSError *error) {
if (error)
NSLog(#"%s: registerUserWithCompletion error: %#", __FUNCTION__, error);
else
NSLog(#"%d", responseValue);
// do whatever you want with that responseValue here, inside the block
}];
// Needless to say, don't try to use the `responseValue` here, because
// `registerUserWithCompletion` runs asynchronously, and you will probably
// hit this line of code well before the above block is executed. Anything
// that is dependent upon the registration must called from within the above
// completion block, not here after the block.
Note, I'd suggest you retire that responseValue property you had before, because now that you're using completion blocks, you get it passed back to you via that mechanism, rather than relying on class properties.
Check this one and use search ;-))
Getting variable from the inside of block
its a lot of duplicates already!
:-)

unrecognised selector error for AFHTTPRequestOperation setAuthenticationAgainstProtectionSpaceBlock:

I'm trying to run the following code in IOS.
AFHTTPRequestOperation *requestOperation = [self.httpClient HTTPRequestOperationWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:self.url.text]]
success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"reply data = %#", [[NSString alloc] initWithData:operation.responseData encoding:NSUTF8StringEncoding]);
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"%#", error);
}];
[requestOperation setAuthenticationAgainstProtectionSpaceBlock:^BOOL(NSURLConnection *connection, NSURLProtectionSpace *protectionSpace) {
return YES;
}];
I then get an error like this:
-[AFHTTPRequestOperation setAuthenticationAgainstProtectionSpaceBlock:]: unrecognized selector sent to instance 0x75a81f0
As far as I can see the block that I'm passing has the correct return type and parameters. What am I doing wrong here?
AFURLRequestOperation conditionally compiles with certain delegate callback methods available, depending on whether _AFNETWORKING_PIN_SSL_CERTIFICATES_ is defined or not.
If it is (which is the default when installing from CocoaPods), setWillSendRequestForAuthenticationChallengeBlock: will be available. Otherwise setAuthenticationAgainstProtectionSpaceBlock: and setAuthenticationChallengeBlock: will be available.
setWillSendRequestForAuthenticationChallengeBlock corresponds to connection:willSendRequest:forAuthenticationChallenge:, which is the preferred delegate method to handle challenges.

AFHTTPRequestOperation blocks UI

I have the following code that fetches some data from a php script. I'm finding that it blocks the interface so that the progress indicator doesn't spin.
Is there an easy way to send this to the background so that the UI is free to do whatever it needs? I was under the impression that using blocks would have achieved this but I was obviously wrong (learning Obj-C one step at a time!)
Thanks!
NSDictionary *params = [NSDictionary dictionaryWithObjectsAndKeys:
self.user.username, #"username",
self.user.password, #"password",
nil];
AFHTTPClient *client = [[AFHTTPClient alloc] initWithBaseURL:
[NSURL URLWithString:#"http://domain.com"]];
// Show animated progress indicator, etc
[self enbleLoadingState:YES];
[client postPath:#"/en/api/login" parameters:params success:^(AFHTTPRequestOperation *operation, id response) {
[self enbleLoadingState:NO];
// Do stuff
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[self enbleLoadingState:NO];
// Show error
}];
This code should work, the request loads in the background. The problem must be somewhere else.
I don't know where the time is spent on the main thread, maybe in enbleLoadingState:? The method starting the request should return immediately. Test this by inserting a NSLog at the start and the end of the method and inside the success-block. The NSLog in the success-block should print out last.