Handling errors

Using try! instead of try in the call to jsonObject(with:options:), you tell the compiler: trust me on this: this method will never fail. Let's write a test that feeds in wrong data and asserts that an error is thrown:

func test_Login_WhenJSONIsInvalid_ReturnsError() { 
  
  
  mockURLSession = MockURLSession(data: Data(), 
                                  urlResponse: nil, 
                                  error: nil) 
  sut.session = mockURLSession 
  
 
  let errorExpectation = expectation(description: "Error") 
  var catchedError: Error? = nil 
  sut.loginUser(withName: "Foo", password: "Bar") { (token, error) in 
    catchedError = error 
    errorExpectation.fulfill() 
  } 
   

  waitForExpectations(timeout: 1) { (error) in 
    XCTAssertNotNil(catchedError) 
  } 
} 

In the test, you feed an empty data object to the completion handler.

Run the tests. The implementation code crashes because the deserialization fails and throws an error. Change the code so that it handles the thrown error correctly. Replace the content of the completion handler with this:

guard let data = data else { return }
do { let dict = try JSONSerialization.jsonObject( with: data, options: []) as? [String:String] let token: Token? if let tokenString = dict?["token"] { token = Token(id: tokenString) } else { token = nil } completion(token, nil) } catch { completion(nil, error) }

With this code, you catch the error if there is one, and pass it to the completion block of the login method. Run the tests. All the tests pass again.

Next, you need to make sure that the implementation calls the completion handler with an error when the data value is nil. Add the following test:

func test_Login_WhenDataIsNil_ReturnsError() { 
 
  
  mockURLSession = MockURLSession(data: nil, 
                                  urlResponse: nil, 
                                  error: nil) 
  sut.session = mockURLSession 
   

  let errorExpectation = expectation(description: "Error") 
  var catchedError: Error? = nil 
  sut.loginUser(withName: "Foo", password: "Bar") { (token, error) in 
    catchedError = error 
    errorExpectation.fulfill() 
  } 
 
  
  waitForExpectations(timeout: 1) { (error) in 
    XCTAssertNotNil(catchedError) 
  } 
} 

Run the test to make sure it fails.

To make the test pass, you need to define the errors to be thrown. Add the following enum to the end of APIClient.swift:

enum WebserviceError : Error { 
  case DataEmptyError 
} 

Replace the guard statement at the beginning of the completion handler in the login method with this:

guard let data = data else { 
  completion(nil, WebserviceError.DataEmptyError) 
  return 
} 

Run the tests. All the tests pass, and there is nothing to refactor.

There is one error left that you need to handle. The completion handler of the data task is called with an error parameter. The web service returns any error that has occurred on the server side in this parameter. Our code has to handle this error. Add the following test to make sure that the implementation handles the error when it is set:

func test_Login_WhenResponseHasError_ReturnsError() { 
  
 
  let error = NSError(domain: "SomeError", 
                      code: 1234, 
                      userInfo: nil) 
  let jsonData = 
"{"token": "1234567890"}"
.data(using: .utf8) mockURLSession = MockURLSession(data: jsonData, urlResponse: nil, error: error)
sut.session = mockURLSession let errorExpectation = expectation(description: "Error") var catchedError: Error? = nil sut.loginUser(withName: "Foo", password: "Bar") { (token, error) in catchedError = error errorExpectation.fulfill() } waitForExpectations(timeout: 1) { (error) in XCTAssertNotNil(catchedError) } }

Note that you only initialize the mock URL session with valid response data. If you pass in nil as data in this test, it would already pass, even though you haven't written the code to handle the response error.

To make this test pass, add the ResponseError case to the WebserviceError enum, and add the following code to the beginning of the completion handler of the data task:

guard error == nil else { 
return completion(nil, error)
}

Run the tests. All the tests pass, and there is nothing to refactor.

There are still some tests and implementations for the APIClient class that are missing. You could add tests to fetch an item from and post an item to the web service, for example, to make it possible to access the to-do items from a web application. We won't add the tests in this book because they would look similar to the tests you have already written. But you should add the tests yourself to practice the TDD workflow.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.226.251.206