Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Fake It ‘Til You Make It: Staying Productive when Working with Web Services

Fake It ‘Til You Make It: Staying Productive when Working with Web Services

Most apps we build are backed by some form of API. When these APIs are pre-existing or bug free, great! What happens when another team is building the API while you are building the app? How can you build a view with data that doesn’t actually exist? We will review some options for staying productive when working with external teams by exploring:

* Best practices for API development with external teams
* Options for testing nonexistent APIs
* Foundation’s URL Loading System to fake API requests that are in development

Josh Johnson

May 10, 2014
Tweet

More Decks by Josh Johnson

Other Decks in Programming

Transcript

  1. Fake It ’Til You Make It Staying Productive when Working

    with Web Services Josh Johnson | @jnjosh | jnjosh.com
  2. Sinatra Using tools like Sinatra you can quickly define API

    endpoints to match tha API spec get '/episodes/:episode' do last_modified episodes.first[:published] content_type :json episodes.select { |e| e[:number] == params[:episode].to_i }.to_json end 㱺 GET http://api.testing.com/episodes/8
  3. Let's download an image! NSString *kittenImageString = @"http://cutekittenimages.com/kittens_cute_kitten.jpg"; NSURL *url

    = [NSURL URLWithString:kittenImageString]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; [request addValue:@"image/jpeg" forHTTPHeaderField:@"Content-Type"]; [NSURLConnection sendAsynchronousRequest:request queue:self.imageQueue completionHandler: ^(NSURLResponse *response, NSData *data, NSError *connectionError) { UIImage *kittenImage = [UIImage imageWithData:data scale:0.0]; [[NSOperationQueue mainQueue] addOperation:[NSBlockOperation blockOperationWithBlock:^{ self.imageView.image = kittenImage; }]]; }];
  4. Implement the Hasselhoff Image Protocol + (BOOL)canInitWithRequest:(NSURLRequest *)request { NSSet

    *validContentTypes = [NSSet setWithArray:@[ @"image/png", @"image/jpg", @"image/jpeg" ]]; return [validContentTypes containsObject:request.allHTTPHeaderFields[@"Content-Type"]]; } + (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request { return request; }
  5. Implement the Hasselhoff Image Protocol - (void)startLoading { id<NSURLProtocolClient> client

    = self.client; NSURLRequest *request = self.request; NSDictionary *headers = @{ @"Content-Type": @"image/jpeg" }; NSData *imageData = UIImageJPEGRepresentation([UIImage imageNamed:@"David_Hasselhoff.jpeg"], 1.0); NSHTTPURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:request.URL statusCode:200 HTTPVersion:@"HTTP/1.1" headerFields:headers]; [client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed]; [client URLProtocol:self didLoadData:imageData]; [client URLProtocolDidFinishLoading:self]; } - (void)stopLoading { // Must be implemented }
  6. Let's check the weather! - (NSOperation *)fetchTemperatureForLatitude:(NSNumber *)latitude longitude:(NSNumber *)longitude

    success:(void (^)(NSNumber *))successBlock failure:(void (^)(NSError *))failureBlock { NSParameterAssert(latitude && ABS(latitude.doubleValue) <= 90.0); NSParameterAssert(longitude && ABS(longitude.doubleValue) <= 180.0); return [self.operationManager GET:@"weather" parameters:@{ @"lat" : latitude, @"lon" : longitude } success:^(AFHTTPRequestOperation *operation, id response) { if (successBlock) { successBlock([response valueForKeyPath:@"main.temp"]); } } failure:^(AFHTTPRequestOperation *operation, NSError *error) { if (failureBlock) { failureBlock(error); } }]; }
  7. A pretty bad unit test - (void)testFetchTemperatureForLatitudeLongitude { __block NSNumber

    *temperature = nil; [self.APIClient fetchTemperatureForLatitude:@(35.99) longitude:@(-78.9) success:^(NSNumber *kelvins) { temperature = kelvins; } failure:nil]; // Assert that temperature != nil before 2.0s elapse UMKAssertTrueBeforeTimeout(2.0, temperature != nil, @"temperature isn't set in time"); }
  8. Let's use URLMock + (void)setUp { [super setUp]; [UMKMockURLProtocol enable];

    [UMKMockURLProtocol setVerificationEnabled:YES]; } + (void)tearDown { [UMKMockURLProtocol setVerificationEnabled:NO]; [UMKMockURLProtocol disable]; [super tearDown]; } - (void)setUp { [super setUp]; [UMKMockURLProtocol reset]; … }
  9. A better unit test, with URLMock - (void)testFetchTemperatureForLatitudeLongitudeCorrectData { //

    setting up latitude, longitude and tempearture to expect NSURL *temperatureURL = [self temperatureURLWithLatitude:latitude longitude:longitude]; [UMKMockURLProtocol expectMockHTTPGetRequestWithURL:temperatureURL responseStatusCode:200 responseJSON:@{ @"main" : @{ @"temp" : temperature } }]; __block BOOL succeeded = NO; __block BOOL failed = NO; __block NSNumber *kelvins = nil; [self.APIClient fetchTemperatureForLatitude:latitude longitude:longitude success:^(NSNumber *temperatureInKelvins) { succeeded = YES; kelvins = temperatureInKelvins; } failure:^(NSError *error) { failed = YES; }]; UMKAssertTrueBeforeTimeout(1.0, succeeded, @"success block is not called"); UMKAssertTrueBeforeTimeout(1.0, !failed, @"failure block is called"); UMKAssertTrueBeforeTimeout(1.0, [kelvins isEqualToNumber:temperature], @"incorrect temperature"); NSError *verificationError = nil; XCTAssertTrue([UMKMockURLProtocol verifyWithError:&verificationError], @"verification failed"); }
  10. Test for errors, with URLMock - (void)testFetchTemperatureForLatitudeLongitudeError { … NSURL

    *temperatureURL = [self temperatureURLWithLatitude:latitude longitude:longitude]; [UMKMockURLProtocol expectMockHTTPGetRequestWithURL:temperatureURL responseError:[self randomError]]; __block BOOL succeeded = NO; __block BOOL failed = NO; [self.APIClient fetchTemperatureForLatitude:latitude longitude:longitude success:^(NSNumber *temperature) { succeeded = YES; } failure:^(NSError *error) { failed = YES; }]; UMKAssertTrueBeforeTimeout(1.0, !succeeded, @"success block is called"); UMKAssertTrueBeforeTimeout(1.0, failed, @"failure block is not called"); NSError *verificationError = nil; XCTAssertTrue([UMKMockURLProtocol verifyWithError:&verificationError], @"verification failed"); }