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

Luis Solano's Slides at NSSpain 2013

Luis Ascorbe
September 19, 2013

Luis Solano's Slides at NSSpain 2013

NSSpain is an iOS & OSX developers conference in Spain.
More info: http://nsspain.com

Luis Ascorbe

September 19, 2013
Tweet

More Decks by Luis Ascorbe

Other Decks in Programming

Transcript

  1. Consuming Web APIs, the TDD way Luis Solano @luisobo My

    name is Luis Solano I work at Pixable, a startup based in NYC leading the iOS team. You can find me on twitter and github. My handler is “luisobo”
  2. Good practices for networking code - How to make it

    more maintainable - Unlike most material on TDD, which only covers basic examples. - Principles that you can apply to any scenario in test-driven development.
  3. Good practices for networking code Tackle real life testing scenarios

    - How to make it more maintainable - Unlike most material on TDD, which only covers basic examples. - Principles that you can apply to any scenario in test-driven development.
  4. Good practices for networking code Tackle real life testing scenarios

    Underlaying principles of TDD - How to make it more maintainable - Unlike most material on TDD, which only covers basic examples. - Principles that you can apply to any scenario in test-driven development.
  5. Good practices for networking code Tackle real life testing scenarios

    Underlaying principles of TDD Better user experience - How to make it more maintainable - Unlike most material on TDD, which only covers basic examples. - Principles that you can apply to any scenario in test-driven development.
  6. - Less loading time for our users - Less errors

    and more meaningful ones. - Potentially less app updates
  7. - Less loading time for our users - Less errors

    and more meaningful ones. - Potentially less app updates
  8. - Less loading time for our users - Less errors

    and more meaningful ones. - Potentially less app updates
  9. Forward compatibility - Ability for the API to evolve without

    breaking existing clients - Consumers of the API enable this.
  10. @interface LSBUser : NSObject @property (nonatomic, strong) NSString *firstName; @property

    (nonatomic, strong) NSString *lastName; @property (nonatomic, strong) NSString *city; // ... @end
  11. LSBUser *user = [[LSBUser alloc] init]; for (NSString *key in

    JSON) { [user setValue:JSON[key] forKey:key]; }
  12. require 'sinatra' require 'json' get "/dogs.json" do content_type :json {

    dogs: [{ name: "perro", color: "brown" },{ name: "tomas", color: "black" }] }.to_json end
  13. it(@"retrieves dogs", ^{ }); [[LSBDogCare new] allDogs:^(NSArray *dogs) { }

    failure:^(NSError *error) { }]; __block NSArray *capturedDogs = nil; capturedDogs = dogs;
  14. it(@"retrieves dogs", ^{ }); [[LSBDogCare new] allDogs:^(NSArray *dogs) { }

    failure:^(NSError *error) { }]; [[expectFutureValue(capturedDogs) shouldEventually] equal:@[@{@"name":@"perro",@"color":@"brown"}, @{@"name":@"tomas",@"color":@"black"} ]]; __block NSArray *capturedDogs = nil; capturedDogs = dogs;
  15. it(@"retrieves dogs", ^{ }); [[LSBDogCare new] allDogs:^(NSArray *dogs) { }

    failure:^(NSError *error) { }]; [[expectFutureValue(capturedDogs) shouldEventually] equal:@[@{@"name":@"perro",@"color":@"brown"}, @{@"name":@"tomas",@"color":@"black"} ]]; __block NSArray *capturedDogs = nil; capturedDogs = dogs;
  16. @implementation LSBDogCare - (void)allDogs:(void(^)(NSArray *dogs))success failure:(void(^)(NSError *error))failure { } @end

    NSURL *url = [NSURL URLWithString:@"http://localhost:4567/dogs.json"]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
  17. @implementation LSBDogCare - (void)allDogs:(void(^)(NSArray *dogs))success failure:(void(^)(NSError *error))failure { } @end

    NSURL *url = [NSURL URLWithString:@"http://localhost:4567/dogs.json"]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; AFJSONRequestOperation *op; op = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, NSDictionary *JSON) { } failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *requestError, id JSON) { }];
  18. @implementation LSBDogCare - (void)allDogs:(void(^)(NSArray *dogs))success failure:(void(^)(NSError *error))failure { } @end

    NSURL *url = [NSURL URLWithString:@"http://localhost:4567/dogs.json"]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; AFJSONRequestOperation *op; op = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, NSDictionary *JSON) { } failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *requestError, id JSON) { }]; success(JSON[@"dogs"]);
  19. @implementation LSBDogCare - (void)allDogs:(void(^)(NSArray *dogs))success failure:(void(^)(NSError *error))failure { } @end

    NSURL *url = [NSURL URLWithString:@"http://localhost:4567/dogs.json"]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; AFJSONRequestOperation *op; op = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, NSDictionary *JSON) { } failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *requestError, id JSON) { }]; success(JSON[@"dogs"]); [op start];
  20. @implementation LSBDogCare - (void)allDogs:(void(^)(NSArray *dogs))success failure:(void(^)(NSError *error))failure { } @end

    NSURL *url = [NSURL URLWithString:@"http://localhost:4567/dogs.json"]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; AFJSONRequestOperation *op; op = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, NSDictionary *JSON) { } failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *requestError, id JSON) { }]; success(JSON[@"dogs"]); [op start];
  21. it(@"retrieves dogs", ^{ __block NSArray *capturedDogs = nil; [[LSBDogCare new]

    allDogs:^(NSArray *dogs) { capturedDogs = dogs; } failure:^(NSError *error) { }]; [[expectFutureValue(capturedDogs) shouldEventually] equal:@[@{@"name":@"perro",@"color":@"brown"}, @{@"name":@"tomas",@"color":@"black"} ]]; }); BAM! We have our first test Raise you hand if you see any problems with this test What is wrong with it? It’s hitting the real network: undeterministic, slow dependency Real life test case Principle TDD: isolate code from undeterministic and slow depencencies.
  22. it(@"retrieves dogs", ^{ __block NSArray *capturedDogs = nil; [[LSBDogCare new]

    allDogs:^(NSArray *dogs) { capturedDogs = dogs; } failure:^(NSError *error) { }]; [[expectFutureValue(capturedDogs) shouldEventually] equal:@[@{@"name":@"perro",@"color":@"brown"}]]; }); BAM! No network! No it’s deterministic, and fast. Raise you hand if you see any problems with this test. What is wrong with it?
  23. it(@"retrieves dogs", ^{ __block NSArray *capturedDogs = nil; [[LSBDogCare new]

    allDogs:^(NSArray *dogs) { capturedDogs = dogs; } failure:^(NSError *error) { }]; [[expectFutureValue(capturedDogs) shouldEventually] equal:@[@{@"name":@"perro",@"color":@"brown"}]]; }); AFJSONRequestOperation *mockOp = [AFJSONRequestOperation mock]; BAM! No network! No it’s deterministic, and fast. Raise you hand if you see any problems with this test. What is wrong with it?
  24. it(@"retrieves dogs", ^{ __block NSArray *capturedDogs = nil; [[LSBDogCare new]

    allDogs:^(NSArray *dogs) { capturedDogs = dogs; } failure:^(NSError *error) { }]; [[expectFutureValue(capturedDogs) shouldEventually] equal:@[@{@"name":@"perro",@"color":@"brown"}]]; }); AFJSONRequestOperation *mockOp = [AFJSONRequestOperation mock]; [AFJSONRequestOperation stub:@selector(JSONRequestOperationWithRequest:success:failure:) withBlock:^id(NSArray *params) { return mockOp; }]; BAM! No network! No it’s deterministic, and fast. Raise you hand if you see any problems with this test. What is wrong with it?
  25. it(@"retrieves dogs", ^{ __block NSArray *capturedDogs = nil; [[LSBDogCare new]

    allDogs:^(NSArray *dogs) { capturedDogs = dogs; } failure:^(NSError *error) { }]; [[expectFutureValue(capturedDogs) shouldEventually] equal:@[@{@"name":@"perro",@"color":@"brown"}]]; }); AFJSONRequestOperation *mockOp = [AFJSONRequestOperation mock]; [AFJSONRequestOperation stub:@selector(JSONRequestOperationWithRequest:success:failure:) withBlock:^id(NSArray *params) { return mockOp; }]; __block NSURLRequest *capturedRequest; __block void (^capturedSuccess)(NSURLRequest *request, NSHTTPURLResponse *response, id JSON); BAM! No network! No it’s deterministic, and fast. Raise you hand if you see any problems with this test. What is wrong with it?
  26. it(@"retrieves dogs", ^{ __block NSArray *capturedDogs = nil; [[LSBDogCare new]

    allDogs:^(NSArray *dogs) { capturedDogs = dogs; } failure:^(NSError *error) { }]; [[expectFutureValue(capturedDogs) shouldEventually] equal:@[@{@"name":@"perro",@"color":@"brown"}]]; }); AFJSONRequestOperation *mockOp = [AFJSONRequestOperation mock]; [AFJSONRequestOperation stub:@selector(JSONRequestOperationWithRequest:success:failure:) withBlock:^id(NSArray *params) { return mockOp; }]; __block NSURLRequest *capturedRequest; __block void (^capturedSuccess)(NSURLRequest *request, NSHTTPURLResponse *response, id JSON); capturedRequest = params[0]; capturedSuccess = params[1]; BAM! No network! No it’s deterministic, and fast. Raise you hand if you see any problems with this test. What is wrong with it?
  27. it(@"retrieves dogs", ^{ __block NSArray *capturedDogs = nil; [[LSBDogCare new]

    allDogs:^(NSArray *dogs) { capturedDogs = dogs; } failure:^(NSError *error) { }]; [[expectFutureValue(capturedDogs) shouldEventually] equal:@[@{@"name":@"perro",@"color":@"brown"}]]; }); AFJSONRequestOperation *mockOp = [AFJSONRequestOperation mock]; [AFJSONRequestOperation stub:@selector(JSONRequestOperationWithRequest:success:failure:) withBlock:^id(NSArray *params) { return mockOp; }]; [mockOp stub:@selector(start) withBlock:^id(NSArray *params) { capturedSuccess(capturedRequest, [NSHTTPURLResponse mock], @{@"dogs":@[@{@"name":@"perro",@"color":@"brown"}]}); return nil; }]; __block NSURLRequest *capturedRequest; __block void (^capturedSuccess)(NSURLRequest *request, NSHTTPURLResponse *response, id JSON); capturedRequest = params[0]; capturedSuccess = params[1]; BAM! No network! No it’s deterministic, and fast. Raise you hand if you see any problems with this test. What is wrong with it?
  28. it(@"retrieves dogs", ^{ __block NSArray *capturedDogs = nil; [[LSBDogCare new]

    allDogs:^(NSArray *dogs) { capturedDogs = dogs; } failure:^(NSError *error) { }]; [[expectFutureValue(capturedDogs) shouldEventually] equal:@[@{@"name":@"perro",@"color":@"brown"}]]; }); AFJSONRequestOperation *mockOp = [AFJSONRequestOperation mock]; [AFJSONRequestOperation stub:@selector(JSONRequestOperationWithRequest:success:failure:) withBlock:^id(NSArray *params) { return mockOp; }]; [mockOp stub:@selector(start) withBlock:^id(NSArray *params) { capturedSuccess(capturedRequest, [NSHTTPURLResponse mock], @{@"dogs":@[@{@"name":@"perro",@"color":@"brown"}]}); return nil; }]; __block NSURLRequest *capturedRequest; __block void (^capturedSuccess)(NSURLRequest *request, NSHTTPURLResponse *response, id JSON); capturedRequest = params[0]; capturedSuccess = params[1]; BAM! No network! No it’s deterministic, and fast. Raise you hand if you see any problems with this test. What is wrong with it?
  29. it(@"retrieves dogs", ^{ __block NSArray *capturedDogs = nil; [[LSBDogCare new]

    allDogs:^(NSArray *dogs) { capturedDogs = dogs; } failure:^(NSError *error) { }]; [[expectFutureValue(capturedDogs) shouldEventually] equal:@[@{@"name":@"perro",@"color":@"brown"}]]; }); AFJSONRequestOperation *mockOp = [AFJSONRequestOperation mock]; [AFJSONRequestOperation stub:@selector(JSONRequestOperationWithRequest:success:failure:) withBlock:^id(NSArray *params) { return mockOp; }]; [mockOp stub:@selector(start) withBlock:^id(NSArray *params) { capturedSuccess(capturedRequest, [NSHTTPURLResponse mock], @{@"dogs":@[@{@"name":@"perro",@"color":@"brown"}]}); return nil; }]; __block NSURLRequest *capturedRequest; __block void (^capturedSuccess)(NSURLRequest *request, NSHTTPURLResponse *response, id JSON); capturedRequest = params[0]; capturedSuccess = params[1]; The problem is that the test it’s coupled with the implementation details. Let’s see what’s wrong with that.
  30. Test Implementation Refactor Change a test What I actually do

    What actually happens Definition of refactoring.
  31. Test Implementation Refactor Change a test Change implementation What I

    actually do What actually happens Definition of refactoring.
  32. Test Implementation Refactor Change a test Change implementation Shit I

    forgot to stub that What I actually do What actually happens Definition of refactoring.
  33. it(@"retrieves dogs", ^{ __block NSArray *capturedDogs = nil; [[LSBDogCare new]

    allDogs:^(NSArray *dogs) { capturedDogs = dogs; } failure:^(NSError *error) { }]; [[expectFutureValue(capturedDogs) shouldEventually] equal:@[@{@"name":@"perro",@"color":@"brown"}]]; }); AFJSONRequestOperation *mockOp = [AFJSONRequestOperation mock]; [AFJSONRequestOperation stub:@selector(JSONRequestOperationWithRequest:success:failure:) withBlock:^id(NSArray *params) { return mockOp; }]; [mockOp stub:@selector(start) withBlock:^id(NSArray *params) { capturedSuccess(capturedRequest, [NSHTTPURLResponse mock], @{@"dogs":@[@{@"name":@"perro",@"color":@"brown"}]}); return nil; }]; __block NSURLRequest *capturedRequest; __block void (^capturedSuccess)(NSURLRequest *request, NSHTTPURLResponse *response, id JSON); capturedRequest = params[0]; capturedSuccess = params[1]; Then, how can we test this?
  34. it(@"retrieves dogs", ^{ __block NSArray *capturedDogs = nil; [[LSBDogCare new]

    allDogs:^(NSArray *dogs) { capturedDogs = dogs; } failure:^(NSError *error) { }]; [[expectFutureValue(capturedDogs) shouldEventually] equal:@[@{@"name":@"perro",@"color":@"brown"}, @{@"name":@"tomas",@"color":@"black"}]]; });
  35. it(@"retrieves dogs", ^{ __block NSArray *capturedDogs = nil; [[LSBDogCare new]

    allDogs:^(NSArray *dogs) { capturedDogs = dogs; } failure:^(NSError *error) { }]; [[expectFutureValue(capturedDogs) shouldEventually] equal:@[@{@"name":@"perro",@"color":@"brown"}, @{@"name":@"tomas",@"color":@"black"}]]; }); stubRequest(@"GET", @"http://localhost:4567/dogs.json")
  36. it(@"retrieves dogs", ^{ __block NSArray *capturedDogs = nil; [[LSBDogCare new]

    allDogs:^(NSArray *dogs) { capturedDogs = dogs; } failure:^(NSError *error) { }]; [[expectFutureValue(capturedDogs) shouldEventually] equal:@[@{@"name":@"perro",@"color":@"brown"}, @{@"name":@"tomas",@"color":@"black"}]]; }); stubRequest(@"GET", @"http://localhost:4567/dogs.json") .andReturn(200)
  37. it(@"retrieves dogs", ^{ __block NSArray *capturedDogs = nil; [[LSBDogCare new]

    allDogs:^(NSArray *dogs) { capturedDogs = dogs; } failure:^(NSError *error) { }]; [[expectFutureValue(capturedDogs) shouldEventually] equal:@[@{@"name":@"perro",@"color":@"brown"}, @{@"name":@"tomas",@"color":@"black"}]]; }); stubRequest(@"GET", @"http://localhost:4567/dogs.json") .andReturn(200) .withHeaders(@{ @"Content-Type": @"application/json;charset=utf-8" })
  38. it(@"retrieves dogs", ^{ __block NSArray *capturedDogs = nil; [[LSBDogCare new]

    allDogs:^(NSArray *dogs) { capturedDogs = dogs; } failure:^(NSError *error) { }]; [[expectFutureValue(capturedDogs) shouldEventually] equal:@[@{@"name":@"perro",@"color":@"brown"}, @{@"name":@"tomas",@"color":@"black"}]]; }); stubRequest(@"GET", @"http://localhost:4567/dogs.json") .andReturn(200) .withHeaders(@{ @"Content-Type": @"application/json;charset=utf-8" }) .withBody([@{@"dogs":@[ @{@"name":@"perro",@"color":@"brown"}, @{@"name":@"tomas",@"color":@"black"}] } JSONString]);
  39. it(@"retrieves dogs", ^{ __block NSArray *capturedDogs = nil; [[LSBDogCare new]

    allDogs:^(NSArray *dogs) { capturedDogs = dogs; } failure:^(NSError *error) { }]; [[expectFutureValue(capturedDogs) shouldEventually] equal:@[@{@"name":@"perro",@"color":@"brown"}, @{@"name":@"tomas",@"color":@"black"}]]; }); stubRequest(@"GET", @"http://localhost:4567/dogs.json") .andReturn(200) .withHeaders(@{ @"Content-Type": @"application/json;charset=utf-8" }) .withBody([@{@"dogs":@[ @{@"name":@"perro",@"color":@"brown"}, @{@"name":@"tomas",@"color":@"black"}] } JSONString]);
  40. Don’t couple test with implementation details Don’t test private methods

    Test external behavior of your modules Otherwise you will live in a tested trap. You won’t be able to leverage your tests to refactor.
  41. context(@"when retrieving dogs fail because access was revoked", ^{ it(@"reports

    an error", ^{ }); }); __block NSError *capturedError; [[LSBDogCare new] allDogs:^(NSArray *dogs) { } failure:^(NSError *error) { capturedError = error; }];
  42. context(@"when retrieving dogs fail because access was revoked", ^{ it(@"reports

    an error", ^{ }); }); __block NSError *capturedError; [[LSBDogCare new] allDogs:^(NSArray *dogs) { } failure:^(NSError *error) { capturedError = error; }]; [[expectFutureValue(capturedError) shouldEventually] equal:[NSError errorWithDomain:@"com.luisobo.dogcare" code:23 userInfo:@{ NSLocalizedDescriptionKey: @"uh uh uh, you didn't say the magic word" }]];
  43. context(@"when retrieving dogs fail because access was revoked", ^{ it(@"reports

    an error", ^{ }); }); __block NSError *capturedError; [[LSBDogCare new] allDogs:^(NSArray *dogs) { } failure:^(NSError *error) { capturedError = error; }]; [[expectFutureValue(capturedError) shouldEventually] equal:[NSError errorWithDomain:@"com.luisobo.dogcare" code:23 userInfo:@{ NSLocalizedDescriptionKey: @"uh uh uh, you didn't say the magic word" }]]; stubRequest(@"GET", @"http://localhost:4567/dogs.json") .andReturn(401);
  44. context(@"when retrieving dogs fail because access was revoked", ^{ it(@"reports

    an error", ^{ }); }); __block NSError *capturedError; [[LSBDogCare new] allDogs:^(NSArray *dogs) { } failure:^(NSError *error) { capturedError = error; }]; [[expectFutureValue(capturedError) shouldEventually] equal:[NSError errorWithDomain:@"com.luisobo.dogcare" code:23 userInfo:@{ NSLocalizedDescriptionKey: @"uh uh uh, you didn't say the magic word" }]]; stubRequest(@"GET", @"http://localhost:4567/dogs.json") .andReturn(401);
  45. - (void)allDogs:(void(^)(NSArray *dogs))success failure:(void(^)(NSError *error))failure { // ... op =

    [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, NSDictionary *JSON) { success(JSON[@"dogs"]); } failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) { }]; [op start]; }
  46. - (void)allDogs:(void(^)(NSArray *dogs))success failure:(void(^)(NSError *error))failure { // ... op =

    [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, NSDictionary *JSON) { success(JSON[@"dogs"]); } failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) { }]; [op start]; } if (response.statusCode == 401) { error = [NSError errorWithDomain:@"com.luisobo.dogcare" code:23 userInfo:@{ NSLocalizedDescriptionKey: @"uh uh uh, you didn't say the magic word" }]; } failure(error);
  47. - (void)allDogs:(void(^)(NSArray *dogs))success failure:(void(^)(NSError *error))failure { // ... op =

    [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, NSDictionary *JSON) { success(JSON[@"dogs"]); } failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) { }]; [op start]; } if (response.statusCode == 401) { error = [NSError errorWithDomain:@"com.luisobo.dogcare" code:23 userInfo:@{ NSLocalizedDescriptionKey: @"uh uh uh, you didn't say the magic word" }]; } failure(error);
  48. context(@"when the request fails because there is no internet", ^{

    it(@"reports an error", ^{ }); }); __block NSError *capturedError; [[LSBDogCare new] allDogs:^(NSArray *dogs) { } failure:^(NSError *error) { capturedError = error; }];
  49. context(@"when the request fails because there is no internet", ^{

    it(@"reports an error", ^{ }); }); __block NSError *capturedError; [[LSBDogCare new] allDogs:^(NSArray *dogs) { } failure:^(NSError *error) { capturedError = error; }]; [[expectFutureValue(capturedError) shouldEventually] equal:[NSError errorWithDomain:@"com.luisobo.dogcare" code:446 userInfo:@{ NSLocalizedDescriptionKey: @"eeeer, check your internet dumbass" }]];
  50. context(@"when the request fails because there is no internet", ^{

    it(@"reports an error", ^{ }); }); __block NSError *capturedError; [[LSBDogCare new] allDogs:^(NSArray *dogs) { } failure:^(NSError *error) { capturedError = error; }]; [[expectFutureValue(capturedError) shouldEventually] equal:[NSError errorWithDomain:@"com.luisobo.dogcare" code:446 userInfo:@{ NSLocalizedDescriptionKey: @"eeeer, check your internet dumbass" }]]; stubRequest(@"GET", @"http://localhost:4567/dogs.json") .andFailWithError([NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorCannotConnectToHost userInfo:nil]);
  51. context(@"when the request fails because there is no internet", ^{

    it(@"reports an error", ^{ }); }); __block NSError *capturedError; [[LSBDogCare new] allDogs:^(NSArray *dogs) { } failure:^(NSError *error) { capturedError = error; }]; [[expectFutureValue(capturedError) shouldEventually] equal:[NSError errorWithDomain:@"com.luisobo.dogcare" code:446 userInfo:@{ NSLocalizedDescriptionKey: @"eeeer, check your internet dumbass" }]]; stubRequest(@"GET", @"http://localhost:4567/dogs.json") .andFailWithError([NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorCannotConnectToHost userInfo:nil]);
  52. context(@"when the request fails because there is no internet", ^{

    it(@"reports an error", ^{ }); }); __block NSError *capturedError; [[LSBDogCare new] allDogs:^(NSArray *dogs) { } failure:^(NSError *error) { capturedError = error; }]; [[expectFutureValue(capturedError) shouldEventually] equal:[NSError errorWithDomain:@"com.luisobo.dogcare" code:446 userInfo:@{ NSLocalizedDescriptionKey: @"eeeer, check your internet dumbass" }]]; stubRequest(@"GET", @"http://localhost:4567/dogs.json") .andFailWithError([NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorCannotConnectToHost userInfo:nil]);
  53. 1. Developer screw-ups Developer mistake - Passed nil as an

    argument. - Fail fast and often - Beyond that point, the behavior is not defined. - Assert until it bleeds - Crash: BAM! IN YO FACE!
  54. 1. Developer screw-ups 2. Everything else Developer mistake - Passed

    nil as an argument. - Fail fast and often - Beyond that point, the behavior is not defined. - Assert until it bleeds - Crash: BAM! IN YO FACE!
  55. 1. Automatic recoverable errors (e.g. reauthorize) 2. User recoverable errors

    (e.g. validation, ask for more permissions) 3. Known non-recoverable errors. (e.g. server is down, rate limiting)
  56. 1. Automatic recoverable errors (e.g. reauthorize) 2. User recoverable errors

    (e.g. validation, ask for more permissions) 3. Known non-recoverable errors. (e.g. server is down, rate limiting) 4. “How the fu*k did I get here?” errors
  57. NSError = domain + code Tuple to uniquely identify an

    error. Don’t check for error codes. You want to know how to react to an error Ask the error for the recovery strategy
  58. @interface NSError (FacebookAPI) @end Let’s say that we are implementing

    the Facebook SDK used privately forms an error from a response
  59. @interface NSError (FacebookAPI) @end + (instancetype)facebookAPIErrorWithResponse:(NSDictionary *)response; Let’s say that

    we are implementing the Facebook SDK used privately forms an error from a response
  60. @interface NSError (FacebookAPI) @end + (instancetype)facebookAPIErrorWithResponse:(NSDictionary *)response; - (BOOL)isFacebookAPIError; -

    (BOOL)shouldRecoverByReauthorizing; Let’s say that we are implementing the Facebook SDK used privately forms an error from a response
  61. @interface NSError (FacebookAPI) @end + (instancetype)facebookAPIErrorWithResponse:(NSDictionary *)response; - (BOOL)isFacebookAPIError; -

    (BOOL)shouldRecoverByReauthorizing; - (BOOL)shouldRecoverByRequestingMorePermissions; Let’s say that we are implementing the Facebook SDK used privately forms an error from a response
  62. @interface NSError (FacebookAPI) @end + (instancetype)facebookAPIErrorWithResponse:(NSDictionary *)response; - (BOOL)isFacebookAPIError; -

    (BOOL)shouldRecoverByReauthorizing; - (BOOL)shouldRecoverByRequestingMorePermissions; - (BOOL)shouldRecoverByNotifyingUser; Let’s say that we are implementing the Facebook SDK used privately forms an error from a response
  63. failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) { }

    1. Automatic recoverable errors (reauthorize) 2. User recoverable errors (validation)
  64. failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) { }

    NSError *facebookError = [NSError facebookAPIErrorWithResponse:JSON]; 1. Automatic recoverable errors (reauthorize) 2. User recoverable errors (validation)
  65. failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) { }

    NSError *facebookError = [NSError facebookAPIErrorWithResponse:JSON]; if ([facebookError shouldRecoverByReauthorizing]) { // Reauthorize } 1. Automatic recoverable errors (reauthorize) 2. User recoverable errors (validation)
  66. failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) { }

    NSError *facebookError = [NSError facebookAPIErrorWithResponse:JSON]; if ([facebookError shouldRecoverByReauthorizing]) { // Reauthorize } else if ([facebookError shouldRecoverByRequestingMorePermissions]) { // Request more permissions } 1. Automatic recoverable errors (reauthorize) 2. User recoverable errors (validation)
  67. failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) { }

    NSError *facebookError = [NSError facebookAPIErrorWithResponse:JSON]; if ([facebookError shouldRecoverByReauthorizing]) { // Reauthorize } else if ([facebookError shouldRecoverByRequestingMorePermissions]) { // Request more permissions } else if ([facebookError shouldRecoverByNotifyingUser]) { } 1. Automatic recoverable errors (reauthorize) 2. User recoverable errors (validation)
  68. @interface NSError (FacebookSDK) + (instancetype)facebookSDKErrorWithError:(NSError *)error; - (BOOL)isFacebookSDKError; // ...

    Same deal @end Higher level of abstraction Different domain User friendly message Use NSUnderlayingErrorKey
  69. failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) { }

    NSError *facebookError = [NSError facebookAPIErrorWithResponse:JSON]; if ([facebookError shouldRecoverByReauthorizing]) { // Reauthorize } else if ([facebookError shouldRecoverByRequestingMorePermissions]) { // Request more permissions } else if ([facebookError shouldRecoverByNotifyingUser]) { } 3. Known non-recoverable errors. (e.g. server is down, rate limiting) 4. “How the fu*k did I get here?” errors log remotely - alternative to crash reports TIME? More slides?
  70. failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) { }

    NSError *facebookError = [NSError facebookAPIErrorWithResponse:JSON]; if ([facebookError shouldRecoverByReauthorizing]) { // Reauthorize } else if ([facebookError shouldRecoverByRequestingMorePermissions]) { // Request more permissions } else if ([facebookError shouldRecoverByNotifyingUser]) { } failure([NSError facebookSDKErrorWithError:error]); 3. Known non-recoverable errors. (e.g. server is down, rate limiting) 4. “How the fu*k did I get here?” errors log remotely - alternative to crash reports TIME? More slides?
  71. failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) { }

    NSError *facebookError = [NSError facebookAPIErrorWithResponse:JSON]; if ([facebookError shouldRecoverByReauthorizing]) { // Reauthorize } else if ([facebookError shouldRecoverByRequestingMorePermissions]) { // Request more permissions } else if ([facebookError shouldRecoverByNotifyingUser]) { } else { failure([NSError genericError]); } failure([NSError facebookSDKErrorWithError:error]); 3. Known non-recoverable errors. (e.g. server is down, rate limiting) 4. “How the fu*k did I get here?” errors log remotely - alternative to crash reports TIME? More slides?
  72. failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) { }

    NSError *facebookError = [NSError facebookAPIErrorWithResponse:JSON]; if ([facebookError shouldRecoverByReauthorizing]) { // Reauthorize } else if ([facebookError shouldRecoverByRequestingMorePermissions]) { // Request more permissions } else if ([facebookError shouldRecoverByNotifyingUser]) { } else { failure([NSError genericError]); } failure([NSError facebookSDKErrorWithError:error]); // Ensure consistent state 3. Known non-recoverable errors. (e.g. server is down, rate limiting) 4. “How the fu*k did I get here?” errors log remotely - alternative to crash reports TIME? More slides?
  73. failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) { }

    NSError *facebookError = [NSError facebookAPIErrorWithResponse:JSON]; if ([facebookError shouldRecoverByReauthorizing]) { // Reauthorize } else if ([facebookError shouldRecoverByRequestingMorePermissions]) { // Request more permissions } else if ([facebookError shouldRecoverByNotifyingUser]) { } else { failure([NSError genericError]); } failure([NSError facebookSDKErrorWithError:error]); // Ensure consistent state // Log facebookError in a remote service 3. Known non-recoverable errors. (e.g. server is down, rate limiting) 4. “How the fu*k did I get here?” errors log remotely - alternative to crash reports TIME? More slides?
  74. Build an abstraction on top of of an stateless wrapper

    Take forward compatibility into account Recap
  75. Build an abstraction on top of of an stateless wrapper

    Take forward compatibility into account Isolate test from undeterministic and slow dependencies. Recap
  76. Build an abstraction on top of of an stateless wrapper

    Take forward compatibility into account Isolate test from undeterministic and slow dependencies. Don’t modify implementation and tests at the same time. Recap
  77. Build an abstraction on top of of an stateless wrapper

    Take forward compatibility into account Isolate test from undeterministic and slow dependencies. Don’t modify implementation and tests at the same time. Don’t couple test with implementation details Recap
  78. Build an abstraction on top of of an stateless wrapper

    Take forward compatibility into account Isolate test from undeterministic and slow dependencies. Don’t modify implementation and tests at the same time. Don’t couple test with implementation details Group errors by recover strategy Recap
  79. Build an abstraction on top of of an stateless wrapper

    Take forward compatibility into account Isolate test from undeterministic and slow dependencies. Don’t modify implementation and tests at the same time. Don’t couple test with implementation details Group errors by recover strategy Recap