We’ve TDD’d a class that should handle authentication to the Marvel API. It looks good in theory. But how do we know if it really works?
In addition to TDD-ing part of a system, it’s important to get feedback about the system as a whole. So before we go on to write code to request Marvel comic book characters by name, let’s make sure the authentication class works at all.

Improve your test writing “Flow.”
Sign up to get my test-oriented code snippets.
We’ll do that with an acceptance test.
If I’d been a little more on my game, we could have written the failing acceptance test before beginning our TDD. The acceptance test would drive the TDD from the outside-in. Once the acceptance test passed, we’d know the code was functional. That top-down approach is called Acceptance Test-Driven Development, or ATDD.
But I ended up doing a bottom-up approach. So let’s add an acceptance test now.
[This post is part of the series TDD Sample App: The Complete Collection …So Far]
Keep Acceptance Tests Separate
This acceptance test will do real networking. That will be too slow to keep with the unit tests, because we don’t want to kill the speed of our TDD feedback loop.
So let’s create a new test target called AcceptanceTests. I don’t like having a bunch of Info.plist files, so I set all my test targets to use the same Info.plist. (While writing this post, I decided a better naming scheme would be App-Info.plist and Tests-Info.plist.)
Acceptance Test First Draft
Here’s the first draft of the acceptance test. It fires off an NSURLSessionDataTask. To avoid relying on actual results which could change as Marvel updates their database, we just check for an HTTP status code of 200 OK, meaning the request was successful:
- (void)testCallToMarvel_ShouldGetStatus200OK
{
QCOMarvelAuthentication *sut = [[QCOMarvelAuthentication alloc] init];
NSString *URLString = [@"http://gateway.marvel.com/v1/public/characters?nameStartsWith=Spider"
stringByAppendingString:[sut URLParameters]];
NSURL *url = [NSURL URLWithString:URLString];
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig
delegate:nil
delegateQueue:nil];
__block NSHTTPURLResponse *httpResponse;
NSURLSessionDataTask *dataTask = [session dataTaskWithURL:url
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
httpResponse = (NSHTTPURLResponse *)response;
}];
[dataTask resume];
assertThatAfter(5, futureValueOf(@(httpResponse.statusCode)), is(@200));
}
Testing Asynchronous Code
When testing asynchronous code, you need a way to have the test wait for the results (up to a certain timeout).
XCTest now directly supports testing of asynchronous operations. But the XCTest approach feels really cumbersome to me. Instead, I’m using OCHamcrest’s assertThatAfter to wait up to 5 seconds for the asynchronous call to succeed. I find this easier to read. Other third-party testing frameworks offer similar features.
Refactor the Test for Readability
The test passes: yay! But I’m not satisfied with how the test reads. By their nature, acceptance tests are more complicated than unit tests, so it’s even more important to work on readability. Also, we want the test to resemble the way we want the production code to look.
So instead of having a URLString variable that packages everything together in one shot, I broke off a validQueryMissingAuthentication. Instead of sut (System Under Test), I use authentication. I assemble the two pieces into a validQueryURL. (See 257f6ed.)
I also renamed the test from testCallToMarvel_ShouldGetStatus200OK to the more explicit testValidCallToMarvel_ShouldGetHTTPStatusCode200.
There’s a fair amount of boilerplate networking code there. It’s unlikely to look like that in real code. Let’s extract that into a helper method -startGETRequestToURL:withCompletionHandler:.
Now the test looks like this:
- (void)testValidCallToMarvel_ShouldGetHTTPStatusCode200
{
NSString *validQueryMissingAuthentication = @"http://gateway.marvel.com/v1/public/characters?nameStartsWith=Spider";
QCOMarvelAuthentication *authentication = [[QCOMarvelAuthentication alloc] init];
NSURL *validQueryURL = [NSURL URLWithString:
[validQueryMissingAuthentication stringByAppendingString:[authentication URLParameters]]];
__block NSHTTPURLResponse *httpResponse;
[self startGETRequestToURL:validQueryURL
withCompletionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
httpResponse = (NSHTTPURLResponse *)response;
}];
assertThatAfter(5, futureValueOf(@(httpResponse.statusCode)), is(@200));
}
Working and Readable ⇒ Confident and Documented
By seeing this acceptance test pass, my confidence in the authentication code has increased. I know that it works in a real networking call to the Marvel API. So now we’re ready to use it to build the actual calls.
But I don’t want acceptance tests to be a mass of gobbledygook code. Tests should express what is important, and hide what is unimportant. Through better naming and extracting simple helper methods (just one level), I have a test that is more expressive and better as documentation.
Have you written any networking acceptance tests? How have they helped you? Have you refactored the test code to make it more expressive? Let us know in the comments below.
[This post is part of the series TDD Sample App: The Complete Collection …So Far]
Tests should express what is important, and hide what is unimportant.
Hello everyone!
Assertion for async code changed in OCHamcrest :
assertThatAfter(5, futureValueOf(@(httpResponse.statusCode)), is(@200));
now is:
assertWithTimeout(5, thatEventually(@(httpResponse.statusCode)), is(@200));
Quite right. Thank you, Oleg!