Asynchronous GHUnit Tests & Objective C Foo!
GHUnit is quite useful for running and reporting tests (unit or otherwise) for iOS projects, and I’ve been using it for a while with good results. Recently, however, I found as I wrote more and more integration-style tests with a remote HTTP service I found the code getting to be a pain to write and maintain. There were two reasons for this:
GHAssert variants fail with an Exception, and therefore other tests do not continue to execute.
This was a real drag, since the assert macros are pretty handy, but I want all my tests to run, even if some fail (gasp I know!).
So, I ended up replacing the GHAsserts with a simple conditional, which doesn’t feel as clean, but navigates around the issue.
Selector names became copy and paste heavy.
Since I’m making notification calls like [self notify:kGHUnitWaitStatusSuccess forSelector:@selector(testAsynchronousOperation)];
from within the same method it was additional grunt work to copy the selector name to all the places I needed a reference to the selector.
I thought of adding a SEL mySelector = @selector(foo);
to each method, which would cut down on the copy & pasting, but that just didn’t seem clean to me.
I discovered there is an Objective C variable like self
called _cmd
* which is a reference to the current selector(!). That’s cool, and simplifies code like my testing code a lot.
Sample
For the specific kinds of test cases I was writing, here’s an example which shows both of these issues resolved:
#import <GHUnitIOS/GHUnit.h> | |
@interface RemoteDataServiceTest : GHAsyncTestCase { } | |
@end | |
@implementation RemoteDataServiceTest | |
- (BOOL)shouldRunOnMainThread | |
{ | |
// By default NO, but if you have a UI test or test dependent on running on the main thread return YES. | |
// Also an async test that calls back on the main thread, you'll probably want to return YES. | |
return NO; | |
} | |
- (void)testAsynchronousOperation | |
{ | |
// Call prepare to setup the asynchronous action. | |
// This helps in cases where the action is synchronous and the | |
// action occurs before the wait is actually called. | |
[self prepare]; | |
[RemoteDataService doNiftyStuffRemotelyWithCompletedBlock:^(id result){ | |
if (!result) | |
{ | |
GHTestLog(@"result from remote service was unexpectedly nil."); | |
[self notify:kGHUnitWaitStatusFailure forSelector:_cmd]; | |
return; | |
} | |
GHTestLog(@"We got back: %@", result); | |
[self notify:kGHUnitWaitStatusSuccess forSelector:_cmd]; | |
} errorBlock:^(NSError *error){ | |
GHTestLog(@"Error while doing nifty stuff: %@", [error localizedDescription]); | |
[self notify:kGHUnitWaitStatusFailure forSelector:_cmd]; | |
}]; | |
// Wait until notify called for timeout (seconds); If notify is not called with kGHUnitWaitStatusSuccess then | |
// we will throw an error. | |
[self waitForStatus:kGHUnitWaitStatusSuccess timeout:30.0]; | |
} |