James Zaghini
James Zaghini

Reputation: 4001

GHUnit crashes when running assertion in callback block instead of showing error on front end

Using any assertion in a callback makes the GH-Unit app crash. Assertions work fine elsewhere.

There is a similar question here: Why does a false assertion in async test in GHUnit crash the app instead of just failing the test?

But I don't understand how I can use this solution in my case.

- (void)testLoadMyProfile {

    void(^successCallback)(NSString*);
    successCallback = ^(NSString* response) {

        NSRange textRange;
        textRange =[[response lowercaseString] rangeOfString:[@"syntactically incorrect" lowercaseString]];

        if(textRange.location != NSNotFound) {
            GHFail(@"the request was syntactically incorrect");
        }

        NSDictionary *d;        
        @try {
            d = [response JSONValue];    
        } @catch (NSException *exception) {
            GHAssertNotNil(d, @"The response was not a valid JSONValue");
        }

        GHAssertNotNil([d objectForKey:@"memberId"], @"memberId wasn't in response");
        GHAssertNotNil([d objectForKey:@"profile"], @"profile wasn't in response");
        GHAssertNotNil([d objectForKey:@"name"], @"profile wasn't in response");
        GHAssertNotNil([d objectForKey:@"surnamez"], @"profile wasn't in response");
    };

    void(^errorCallback)(NSString*);
    errorCallback = ^(NSString* response) {
        GHFail(@"the error callback was called");
    };    

    // this is using ASIHTTPRequest to retrieve data
    [[RestAPIConnector sharedInstance] loadMyProfile:successCallback :errorCallback];
}

I can stop the app from crashing by overwriting this method - I could even log the exception, but the test doesn't display as failed on the front end. Ideally, I'd like it to display on the front end so non technical people can run the test and see that everything is working.

- (void)failWithException:(NSException *)exception {

}

Upvotes: 2

Views: 527

Answers (2)

toastedtoast
toastedtoast

Reputation: 76

The answer (you were pointing to) here should work for you, too.

So you should implement GHAsyncTestCase as Travis mentioned before. Having the Async base class you can use waitForStatus:timeout: and the corresponding notify:forSelector: methods. All assertions need to be done after waitForStatus:timeout:. This method pauses the runloop and waits until your callback is done.

Take a look at the samples of GHUnit if you need more info about the async tests.

So in your case i would try something like this:

- (void)testLoadMyProfile {

//Local variable for later assertion. __block is necessary to use the variable in the block.  
__block NSDictionary *d = nil;  

//Preparing.  
[self prepare];

void(^successCallback)(NSString*);
successCallback = ^(NSString* response) {

    NSRange textRange;
    textRange =[[response lowercaseString] rangeOfString:[@"syntactically incorrect" lowercaseString]];

    if(textRange.location != NSNotFound) {
        GHFail(@"the request was syntactically incorrect");
    }

    // Declared before.
    // NSDictionary *d;

    @try {

        d = [response JSONValue];    

    } @catch (NSException *exception) {

        // We'll check that later.
        // GHAssertNotNil(d, @"The response was not a valid JSONValue");
    }

    // Later.
    //GHAssertNotNil([d objectForKey:@"memberId"], @"memberId wasn't in response");
    //GHAssertNotNil([d objectForKey:@"profile"], @"profile wasn't in response");
    //GHAssertNotNil([d objectForKey:@"name"], @"profile wasn't in response");
    //GHAssertNotNil([d objectForKey:@"surnamez"], @"profile wasn't in response");

    // we notify ourself that we are done. selector should point to this method!  
   [self notify:kGHUnitWaitStatusSuccess forSelector:@selector(testLoadMyProfile)];  

};

void(^errorCallback)(NSString*);
errorCallback = ^(NSString* response) {
    GHFail(@"the error callback was called");

    // in this case we do notify ourself that the request failed.  
   [self notify:kGHUnitWaitStatusFailure forSelector:@selector(testLoadMyProfile)];

};    

[[RestAPIConnector sharedInstance] loadMyProfile:successCallback :errorCallback];

// This line pauses the runloop for the length of the timeout
[self waitForStatus:kGHUnitWaitStatusSuccess timeout:10.0];

// And finally after this line you can do your assertions. As the code pauses until you notify it or the timeout fires.
GHAssertNotNil(d, @"The response was not a valid JSONValue");
GHAssertNotNil([d objectForKey:@"memberId"], @"memberId wasn't in response");
GHAssertNotNil([d objectForKey:@"profile"], @"profile wasn't in response");
GHAssertNotNil([d objectForKey:@"name"], @"profile wasn't in response");
GHAssertNotNil([d objectForKey:@"surnamez"], @"profile wasn't in response");

}

So all assertions for asynchronous tests need to done after waitForStatus:timeout:. and don't forget the notify:forSelector.

Upvotes: 0

Travis Worm
Travis Worm

Reputation: 166

You should change Status to stop the block run loop, check my comments: (Sorry for my BAD English)

At First, you must subclass GHAsyncTestCase

-(void)testGetRequest{
    [self prepare]; //prepare for hook the run loop

    PLRequest *req=[PLRequest requestWithURL:[NSURL URLWithString:@"http://m.baidu.com"]];
    req.finishCallback=^(PLRequest *req){
        NSData *d=req.respondData;

        NSString *s=[NSString stringWithUTF8String:[d bytes]];

        GHTestLog(@"Finish:%@", s);
        [self notify:kGHUnitWaitStatusSuccess]; //here to return
    };

    [req start];

    [self waitForStatus:kGHUnitWaitStatusSuccess timeout:15]; //wait for your block change Status then if timeout you will get a 'crash'

}

Upvotes: 1

Related Questions