# SwiftUI - TDD async/await

## Problem: How to TDD async/await ?
### Solution: Stub the response, async function needs to know it's result before call

IMPORTANT: This tutorial is only available for iOS 15+  
It will use the async/await integration in `URLSession`  
And the `AsyncImage` SwiftUI view to load the image  

async/await it new to the in iOS ecosystem  
Will learn here how to TDD it  

Will use the Dog API to test our logic  
It's a simple API no need for private key  
First we will get dogs form the API  
Second download dogs images from returned image url response.  

Here is the result of our implementation  
![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1664828478716/s-ct4_J5d.png align="left")

---
First the list of behaviour to test
<pre>
 ======= LOAD DOGS
[ ] On init view model, status is idle
[ ] On load use the dog API url to get dogs images
[ ] Load dogs update status to loading
    [ ] If Load dogs failed delivers .connectivity error
      [ ] Display an error message (text)
      [ ] User can retry to load (button)
    [ ] Load dogs succeed with 200 response
      [ ] Parsing succeed delivers dogs
      [ ] Extract dogs names from returned url
      [ ] Parsing failed delivers .invalidData error
        [ ] Display an error message (text)
        [ ] User can retry to load (button)
</pre>

---
Start with this first requirement  
It ensure that nothing happen on start  
<pre>
 ======= LOAD DOGS
[ ] On init view model, status is idle
</pre>

As usual write the test first  
```swift
  func test_init_statusIdle() {
    let sut = DogsViewModel()
    
    XCTAssertEqual(sut.status, .idle)
  }
```
1. Need to create the `DogsViewModel`
2. Need to create the status enum and variable 

Fix the error for Xcode to compile    
```swift
class DogsViewModel: ObservableObject {
  enum Status { 
    case idle
  }

  @Published var status = Status.idle
}
```

Run tests  
They are passing  
Can mark this requirement as done.  
<pre>
 ========= LOAD DOGS
[✅] On init view model, status is idle
</pre>
 
---
Continue with this requirement for  
<pre>
 ========= LOAD DOGS
[ ] On load use the dog API url to get dogs images
</pre>

As usual write the test first  
```swift
  func test_loadDogs_performARequestWithDogsURLRequest() async {
    let dogsURLRequest = URLRequest(url: URL(string: "https://dog.ceo/api/breeds/image/random/3")!) // 1
    let urlSessionSpy = URLSessionAsyncAwaitSpy(result: .failure(.connectivity)) // 2
    let networkService = NetworkService(session: urlSessionSpy) // 3
    let dogServiceSpy = DogService(networkService: networkService) // 4
    let sut = DogViewModelSpy(dogService: dogServiceSpy) // 5
    
    try? await sut.loadDogs() // 6
    
    XCTAssertEqual(urlSessionSpy.requests, [dogsURLRequest]) // 7
  }
```
1. Url that returns 3 dogs in a random order. 
2. First we need to create a spy that will use our mock of the URLSession async function. We inject (stub) the response of API (need to complete with error or success. The function must complete or you will be blocked be the `await` that will never return).  
3. The `NetworkService` will request dogs to the API and will return response as `Data`. We inject our `urlSessionSpy` to able to control the result.
4. The `DogService` will turn `Data` into a rich model dogs
5. The `DogViewModel` will use the service to get dogs
6. Use the SUT that perform an async loading of dogs waiting for the response
7. After load complete, check that the right URL has been used

First we need a mock to test the `URLSession`  
For this async function  
![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1664912844154/Yu1Q7kMII.png align="left")

Start creating the `protocol`  
It that looks like the signature of this function  
```swift
protocol AsyncAwait {
  func data(request: URLRequest) async throws -> (Data, URLResponse)
}
```

Implement the mock to `URLSession`  
```swift
extension URLSession: AsyncAwait {
    func data(request: URLRequest) async throws -> (Data, URLResponse) {
        try await self.data(for: request)
    }
}
```

We can now create `URLSessionAsyncAwaitSpy`  
```swift 
  private class URLSessionAsyncAwaitSpy: AsyncAwait {
    var requests = [URLRequest]()
    let result: Result<(Data, URLResponse), NetworkService.Error>
    
    internal init(result: Result<(Data, URLResponse), NetworkService.Error>) {
      self.result = result
    }
    
    func data(request: URLRequest) async throws -> (Data, URLResponse) {
      self.requests.append(request) // 1
      return try result.get() // 2
    }
  }
```
1. We accumulate all requests for this function calls (Spy)
2. We return the result that was injected (Stub)

Second create the `NetworkService`
```swift
class NetworkService {
  private let session: AsyncAwait

  init(session: AsyncAwait = URLSession.shared) {
    self.session = session
  }

  enum Error: Swift.Error {
    case connectivity
  }

  func perform(request: URLRequest) async throws -> (Data, URLResponse) {
    do {
      let (data, response) = try await session.data(request: request)
      return (data, response)
    } catch {
      throw error
    }
  }
}
```

Third create the `DogService`
```swift
class DogService {
  private let networkService: NetworkService
  private let loadDogsRequest = URLRequest(url: URL(string: "https://any-url.com")!) // 1
  
  init(networkService: NetworkService) {
    self.networkService = networkService
  }

  func loadDogs() async throws {
    do {
      _ = try await networkService.perform(request: loadDogsRequest)
    } catch {
      throw error
    }
  }
}
```
1. Enter a wrong URL to see a failed test first

Fourth update `DogsViewModel` injecting the `DogService` and create the `loadDogs()` async function
```swift 
class DogsViewModel: ObservableObject {
  ...
  private let dogService: DogService

  init(dogService: DogService) {
    self.dogService = dogService
  }

  func loadDogs() async throws {
    do {
      try await dogService.loadDogs()
    } catch { 
      throw error
    }
  }
```

Run the test and it fails  
*XCTAssertEqual failed: ("[https://any-url.com]") is not equal to ("[https://dog.ceo/api/breeds/image/random/3]")*

Let’s make it pass using the right URL
```swift 
class DogService {
  ...
  private let loadDogsRequest = URLRequest(url: URL(string: "https://dog.ceo/api/breeds/image/random/3")!)
  ...
}
```

---
The setup to init the SUT is a bit verbose
Let's create an helper function to avoid to always repeat
```swift
  // MARK: - Helpers

  private func makeSUT(
    loadDogsResult: Result<(Data, URLResponse), NetworkService.Error> = .failure(.connectivity)) // 1
  -> (sut: DogsViewModel, urlSessionSpy: URLSessionAsyncAwaitSpy) {
    let urlSessionSpy = URLSessionAsyncAwaitSpy(result: loadDogsResult)
    let networkService = NetworkService(session: urlSessionSpy)
    let dogService = DogService(networkService: networkService)
    let sut = DogsViewModel(dogService: dogService)
    return (sut, urlSessionSpy)
  }
```
1. Inject the mock result of loading dogs on SUT creation. Add a default value to hide details for tests where the result in not relevant.

Refactor the 2 tests with the makeSUT() function
```swift 
  func test_init_statusIdle() {
    let (sut, _) = makeSUT()

    XCTAssertEqual(sut.status, .idle)
  }

  func test_loadDogs_performARequestWithDogsURLRequest() async {
    let dogsURLRequest = URLRequest(url: URL(string: "https://dog.ceo/api/breeds/image/random/3")!)
    let (sut, urlSessionSpy) = makeSUT()
    
    try? await sut.loadDogs()

    XCTAssertEqual(urlSessionSpy.requests, [dogsURLRequest])
  }
```

Run tests  
They are passing  

---
Continue with this requirement for  
<pre>
 ========= LOAD DOGS
   [ ] Load dogs update status to loading
</pre>

As usual write the test first  
```swift
  func test_loadDogs_statusLoading() async {
    let (sut, _) = makeSUT(loadDogsResult: .failure(.connectivity))

    try? await sut.loadDogs()

    XCTAssertEqual(sut.messages, [.status(.loading)]) // 1
  }
```
1. Needs to create the messages property that will capture all status values update.  

Fix the error for Xcode to compile    
First create a `DogsViewModelSpy`  
It captures the status updates  
```swift
  private class DogsViewModelSpy: DogsViewModel {
    enum Message: Equatable {
      static func == (lhs: Message, rhs: Message) -> Bool {
        switch (lhs, rhs) {
        case (status(let status1), status(let status2)):
          return status1 == status2 ? true : false
        }
      }

      case status(DogsViewModel.Status)
    }

    var messages = [Message]()

    override var status: DogsViewModel.Status {
      didSet { messages.append(.status(status)) }
    }
}
```

Update the `DogsViewModel` with the loading status  
Make it `Equatable` to able to compare  
```swift 
class DogsViewModel: ObservableObject {
  enum Status: Equatable { // 1
    static func == (lhs: DogsViewModel.Status, rhs: DogsViewModel.Status) -> Bool {
      switch (lhs, rhs) {
      case (.idle, .idle), (.loading, .loading):
        return true
      default:
        return false
      }
    }

    case idle
    case loading // 2
  }
  ...
}
```
1. Make the enum `Status` conform to `Equatable`, to be able to compare them
2. Add the `loading` case

Run the test and it fails  
*XCTAssertEqual failed: ("[]") is not equal to ("[AsyncAwaitTests.AsyncAwaitTests.(unknown context at $1038858a0).DogsViewModelSpy.Message.status(AsyncAwait.DogsViewModel.Status.loading)]")*

Let’s make it pass on updating the status to loading  
```swift 
class DogsViewModel: ObservableObject {
  ...
  func loadDogs() async throws {
    status = .loading  👈
  ...
``` 

Run tests  
They are passing  
Can mark this requirement as done  
<pre>
 ========= LOAD DOGS
 [✅] Load dogs update status to loading
</pre>

---
Continue with this requirement for error on request API
<pre>
 ========= LOAD DOGS
   [ ] If Load dogs failed delivers .connectivity error
</pre>

As usual write the test first  
```swift
  func test_load_deliversConnectivityErrorOnNetworkError() async throws {
    let connectivityError = NetworkService.Error.connectivity
    let (sut, _) = makeSUT(loadDogsResult: .failure(connectivityError))

    do {
      try await sut.loadDogs()
      XCTFail("Expected to failed") // 1
    } catch {
      XCTAssertEqual(error as? NetworkService.Error, connectivityError)
    }
  }
```
1. We expect a failure here. If request succeed the test must failed.  

Run tests  
They are passing  
With `async` function you always need to complete  
The `await` keyword will block us until success or throws error  
And to be able to write previous test   
I always complete SUT with a .connectivity error  

Can mark this requirement as done.  
<pre>
 ========= LOAD DOGS
 [✅] If Load dogs failed delivers .connectivity error
</pre>

---
Continue with this requirement for display an error message on network error
<pre>
 ========= LOAD DOGS
   [ ] Display an error message (text) on .connectivity error
</pre>

As usual write the test first  
```swift
  func test_load_onRequestError_displayMessageInfo() async {
    let (sut, _) = makeSUT(loadDogsResult: .failure(.connectivity))

    do {
      try await sut.loadDogs()
      XCTFail("Expected to failed")
    } catch {
      // 1
      XCTAssertEqual(sut.error, .loadDogs, "Expect error update or the alert will not be displayed")
      // 2
      XCTAssertEqual(sut.alertTitle(), "Error loading dogs", "Expected a title or the alert will be displayed without it") 
    }
  }
```
1. Need to create the `error` property that is responsible to display an alert on screen
2. Need to create the function `alertTitle()` that return the title of the alert for `.connectivity` error

Fix the error for Xcode to compile    
```swift
class DogsViewModel: ObservableObject {
  ...
  enum Error: String, Swift.Error, Identifiable {
    case loadDogs

    var id: String {
      self.rawValue
    }
  }
  ...
  var error: Error?
  ...
  func loadDogs() async throws {
    status = .loading
    
    do {
      try await dogService.loadDogs()
    } catch let error as NetworkService.Error {
      switch error {
      case .connectivity:
        self.error = .loadDogs 👈
      }
      throw error
    }
  }

  func alertTitle() -> String {
    switch error {
    case .loadDogs:
      return "Error loading dogs"
    case .none:
      return ""
    }
  }
}
```

Run tests  
They are passing  
Can mark this requirement as done.  
<pre>
 ========= LOAD DOGS
 [✅] Display an error message (text) on .connectivity error
</pre>

---
Continue with this requirement for retry on load error  
<pre>
 ========= LOAD DOGS
   [ ] User can retry to load (button) on .connectivity error
</pre>

As usual write the test first  
```swift
  func test_retryLoadDogs_loadDogs() async {
    let (sut, _) = makeSUT(loadDogsResult: .failure(.connectivity))

    await sut.retryLoadDogs() // 1

    XCTAssertEqual(sut.messages, [.status(.loading)])
  }
```
1. Need to create the `retryLoadDogs()` function 

Fix the error for Xcode to compile    
```swift
class DogsViewModel: ObservableObject {
  ...
  func retryLoadDogs() async {
    try? await loadDogs()
  }
  ...
```

Run tests  
They are passing  
Can mark this requirement as done  
<pre>
 ========= LOAD DOGS
 [✅] User can retry to load (button) on .connectivity error
</pre>

---
Continue with this requirement for succeeded to load but return an empty JSON
<pre>
 ========= LOAD DOGS
   [ ] Load dogs succeed with 200 response
</pre>

As usual write the test first  
```swift
  func test_loadHereos_deliversSuccess200HTTPResponseWithEmptyDogsJSON() async throws {
    let emptyDogsJSON = Data("{\"message\": []}".utf8)
    let expectedEmptyDogs = [DogUI]() // 1
    let (sut, _) = makeSUT(loadDogsResult: .success((emptyDogsJSON, httpResponse(with: 200)))) // 2

    try? await sut.loadDogs()
    waitForUICompletion() // 3

    XCTAssertEqual(sut.messages, [.status(.loading), .status(.loaded(dogs: expectedEmptyDogs))]) // 4
  }
```
1. Create the `DogUI` model that represent the dog requirement to display the UI.  
2. Create the `httpResponse(with:)` helper function that simulate an http response.  
3. Create the `waitForUICompletion()` helper function that wait until the async update of UI completes.  
4. Add the `.loaded(dogs:)` status case.  

First the `DogUI` model
```swift
struct DogUI {

}
```

Second create the `httpResponse(with:)` and `waitForUICompletion()` helpers functions
```swift 
class AsyncAwaitTests: XCTestCase {
  ...
  // MARK: - Helpers
  ...
  private func anyURL() -> URL {
    return URL(string: "https://any-url.com")!
  }

  private func httpResponse(with statusCode: Int) -> HTTPURLResponse {
    return HTTPURLResponse(url: anyURL(), statusCode: statusCode, httpVersion: nil, headerFields: nil)!
  }

  private func waitForUICompletion() {
    let expectation = self.expectation(description: "Expect that ui update on the main thread completes")
    DispatchQueue.main.async {
        expectation.fulfill()
    }
    wait(for: [expectation], timeout: 1)
  }
}
```

Fourth add the `.loaded(dogs:)` status case
``` swift 
class DogsViewModel: ObservableObject {
  enum Status: Equatable {
    static func == (lhs: DogsViewModel.Status, rhs: DogsViewModel.Status) -> Bool {
      switch (lhs, rhs) {
      case (.idle, .idle), (.loading, .loading):
        return true
      case (.loaded(dogs: let dogs1), .loaded(dogs: let dogs2)):  👈
        return dogs1 == dogs2 ? true : false // 1
      default:
        return false
      }
    }

    case idle
    case loading
    case loaded(dogs: [DogUI])  👈
  }
  ...
}
```
1. The DogUI needs to conform to `Equatable` to be able to compare
```swift 
struct DogUI: Equatable {
  ...
}
```

Run the test and it fails  
The status .loaded has never been called  
*XCTAssertEqual failed: ("[AsyncAwaitTests.AsyncAwaitTests.(unknown context at $10c899490).DogsViewModelSpy.Message.status(AsyncAwait.DogsViewModel.Status.loading)]") is not equal to ("[AsyncAwaitTests.AsyncAwaitTests.(unknown context at $10c899490).DogsViewModelSpy.Message.status(AsyncAwait.DogsViewModel.Status.loading), AsyncAwaitTests.AsyncAwaitTests.(unknown context at $10c899490).DogsViewModelSpy.Message.status(AsyncAwait.DogsViewModel.Status.loaded(dogs: []))]")*

Let’s make it pass  
Start by update the status with the loaded dogs  
```swift 
class DogsViewModel: ObservableObject {
  ...
  func loadDogs() async throws {
    ...    
    do {
      let dogs = try await dogService.loadDogs()
      DispatchQueue.main.async {
        self.status = .loaded(dogs: dogs)
      }
    } catch let error as NetworkService.Error {
      ...
    }
  }
  ...
}
```
After handle the response   
Check the status code 200  
Parse the object using `Decodable`  
```swift 
class DogService {
    ...
    func loadDogs() async throws -> [DogUI] {
    do {
      _ = try await networkService.perform(request: loadDogsRequest)
      let (data, response) = try await networkService.perform(request: loadDogsRequest)
      if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 {
        let decoder = JSONDecoder()
        do {
          let dogsApi = try decoder.decode(DogsApi.self, from: data) // 1
          let dogs = dogsApi.message.map{ DogUI(url: URL(string: $0)) } // 2
          return dogs
        }
        catch {

        }
      }
    } catch {
      throw error
    }
    
    return []
  }
}
``` 
1. Need to create the `DogApi` model
2. Add the `url` property to the `DogUI` model

First create the `DogApi` model
```swift 
struct DogsApi: Decodable {
  let message: [String]
}
```

Second add the `url` property to the `DogUI` model
```swift 
struct DogUI: Equatable {
  let url: URL?
}
```

Run tests  
They are passing  
Can mark this requirement as done  
<pre>
 ========= LOAD DOGS
 [✅] Load dogs succeed with 200 response
</pre>

---
Continue with this requirement for decodable parsing succeeded
<pre>
 ========= LOAD DOGS
   [ ] Parsing succeed delivers dogs
</pre>

As usual write the test first  
```swift
  func test_loadHereos_deliversDogsOnSuccess200HTTPResponse() async throws {
    let validDogsJSON = Data("{\"message\": [\"https://images.dog.ceo/breeds/husky/n02110185_10116.jpg\"]}".utf8)
    let expectedDogs = DogUI(url: URL(string: "https://images.dog.ceo/breeds/husky/n02110185_10116.jpg")!)
    let (sut, _) = makeSUT(loadDogsResult: .success((validDogsJSON, httpResponse(with: 200))))

    do {
      try await sut.loadDogs()
      waitForUICompletion()

      XCTAssertEqual(sut.status, .loaded(dogs: [expectedDogs]))
    } catch {
      XCTFail("Expected to succeeded, got error \(error) instead")
    }
  }
```

Run tests  
They are passing  
Can mark this requirement as done  
<pre>
 ========= LOAD DOGS
 [✅] Load dogs succeed with 200 response
</pre>

---
Continue with this requirement for dogs names extract from url
<pre>
 ========= LOAD DOGS
   [ ] Extract dogs names from returned url
</pre>

As usual write the test first  
```swift
  func test_dogsName_extractDogsNamesFromPicturesURL() async throws {
    let pictureURL1 = URL(string: "https://images.dog.ceo/breeds/setter-english/n02100735_10038.jpg")!
    let pictureURL2 = URL(string: "https://images.dog.ceo/breeds/komondor/n02105505_1657.jpg")!
    let dog1 = DogUI(url: pictureURL1)
    let dog2 = DogUI(url: pictureURL2)
    let expectDogsName1 = "Setter English"
    let expectDogsName2 = "Komondor"

    XCTAssertEqual(dog1.name, expectDogsName1) // 1
    XCTAssertEqual(dog2.name, expectDogsName2) // 1
  }
```
1. Needs to create the `name` property

Fix the error for Xcode to compile    
```swift
struct DogUI: Equatable {
  let url: URL?
  👇
  var name: String { 
    if let urlString = url?.absoluteString {
      return urlString
        .components(separatedBy: "/")[4]
        .replacingOccurrences(of: "-", with: " ")
        .capitalized
    } else {
      return "Unknown"
    }
  }
}
```

Run tests  
They are passing  
Can mark this requirement as done.  
<pre>
 ========= LOAD DOGS
 [✅] Extract dogs names from returned url
</pre>

---
Continue with this requirement for `Decodable` failure
<pre>
 ========= LOAD DOGS
   [ ] Parsing failed delivers .invalidData error
</pre>

As usual write the test first  
```swift
  func test_loadHereos_deliversErrorOn200HTTPResponseWithInvalidData() async throws {
    let expectedError = NetworkService.Error.invalidData // 1
    let invalidDogsJSON = Data("".utf8)
    let (sut, _) = makeSUT(loadDogsResult: .success((invalidDogsJSON, httpResponse(with: 200))))

    do {
      try await sut.loadDogs()
      XCTFail("Expected to failed the JSON cannot be decoded")
    } catch {
      XCTAssertEqual(error as? NetworkService.Error, expectedError)
    }
  }

```
1. Needs to create the .invalidData error case

Fix the error for Xcode to compile    
```swift
class NetworkService {
  ...
  enum Error: Swift.Error {
    case connectivity
    case invalidData 👈
  }
  ...
}
```

Run the test and it fails  
Handle the new `.invalidData` case
```swift 
class DogsViewModel: ObservableObject {
  ...
  func loadDogs() async throws {
  ...
      switch error {
      case .connectivity:
        self.error = .loadDogs
      case .invalidData:  👈
        break
      }
  ...
  }
}
``` 
Send `.invalidData` error on decode failure
```swift 
class DogService {
  ...
  func loadDogs() async throws -> [DogUI] {
    ...
          do {
          let dogsApi = try decoder.decode(DogsApi.self, from: data)
          let dogs = dogsApi.message.map{ DogUI(url: URL(string: $0)) }
          return dogs
        } catch {
          throw NetworkService.Error.invalidData 👈
        }
    ...
  }
}
```

Run tests  
They are passing  
Can mark this requirement as done  
<pre>
 ========= LOAD DOGS
 [✅] Parsing failed delivers .invalidData error
</pre>

---
Continue with this requirement for message error on .invalidData error
<pre>
 ========= LOAD DOGS
   [ ] Display an error message (text) on .invalidData error
</pre>

As usual write the test first  
```swift
  func test_load_displayErrorMessageoOnInvalidDataError() async throws {
    let expectedErrorText = "Error parsing dogs"
    let (sut, _) = makeSUT(loadDogsResult: .failure(.invalidData))

    XCTAssertEqual(sut.alertTitle(), "", "Expected to get an empty string as default value")

    do {
      try await sut.loadDogs()
      XCTFail("Expected to failed")
    } catch {
      XCTAssertEqual(sut.error, .parsingDogs) // 1
      XCTAssertEqual(sut.alertTitle(), expectedErrorText)
    }
  }

```
1. Needs to create the .parsingDogs error in the view model

Fix the error for Xcode to compile  
Add the .parsingDogs error  
The function `loadDogs` must handle this new case  
Update the `alertTitle()` function with this new case  
```swift
class DogsViewModel: ObservableObject {
  ...
  enum Error: String, Swift.Error, Identifiable {
    case loadDogs
    case parsingDogs 👈
    ...
  func loadDogs() async throws {
      switch error {
      case .connectivity:
        self.error = .loadDogs
      case .invalidData:
        self.error = .parsingDogs 👈
      }
   }
  ...
  func alertTitle() -> String {
    switch error {
    case .loadDogs:
      return "Error loading dogs"
    case .parsingDogs: 👈
      return "Error parsing dogs" 
    case .none:
      return ""
    }
  }
}
```

Run tests  
They are passing  
Can mark this requirement as done.  
<pre>
 ========= LOAD DOGS
 [✅] Display an error message (text) on .invalidData error
</pre>

---
Continue with this requirement for retry on `.invalidData` error
<pre>
 ========= LOAD DOGS
   [ ] User can retry to load (button) on .invalidData error
</pre>

As usual write the test first  
```swift
  func test_loadHereos_onParsingError_userCanRetryToLoad() async {
    let (sut, _) = makeSUT(loadDogsResult: .failure(.invalidData))

    await sut.retryLoadDogs()

    XCTAssertEqual(sut.messages, [.status(.loading)])
  }
```

Run tests  
They are passing  
The `retryLoadDogs()` function already exist 
Can mark this requirement as done.  
<pre>
 ========= LOAD DOGS
 [✅] User can retry to load (button) on .invalidData error
</pre>

---
Now let's create the view that implement the view model
```swift 
struct DogsView: View {
  @ObservedObject var viewModel: DogsViewModel

  var body: some View {
    NavigationView {
      ZStack {
        switch viewModel.status {
        case .idle:
          EmptyView()
        case .loading:
          ProgressView()
        case .loaded(dogs: let dogs):
          ZStack {
            Color(UIColor.secondarySystemBackground)
              .edgesIgnoringSafeArea(.all)
            ScrollView {
              LazyVStack(spacing: 34) {
                ForEach(dogs, id: \.url) { dog in
                  VStack(spacing: 0) {
                    AsyncImage(url: dog.url) { phase in
                      if let image = phase.image {
                        image // Displays the loaded image.
                          .resizable()
                          .scaledToFill()
                          .frame(width: UIScreen.main.bounds.width - 32, height: UIScreen.main.bounds.width, alignment: .center)
                          .clipped()
                      } else if phase.error != nil {
                        Color.red // Indicates an error.
                          .frame(width: UIScreen.main.bounds.width - 32, height: UIScreen.main.bounds.width, alignment: .center)
                      } else {
                        ZStack { // Acts as a placeholder.
                          Color(UIColor.tertiarySystemBackground)
                            .frame(width: UIScreen.main.bounds.width - 32, height: UIScreen.main.bounds.width, alignment: .center)
                          ProgressView()
                            .scaleEffect(4)
                            .progressViewStyle(CircularProgressViewStyle(tint: SwiftUI.Color(UIColor.label).opacity(0.5)))
                        }
                      }
                    }
                    Text(dog.name)
                      .font(.title)
                      .foregroundColor(SwiftUI.Color.primary)
                      .padding()
                  }
                  .background(SwiftUI.Color(UIColor.tertiarySystemBackground))
                  .cornerRadius(20)
                  .shadow(color: Color.black.opacity(0.25), radius: 20, x: 0, y: 3)
                }
              }
            }
            .padding(.top, 32)
          }
        }
      }
      .navigationTitle("Dogs")
    }
    .task {
      try? await viewModel.loadDogs()
    }
    .alert(item: $viewModel.error) { _ in
      return Alert(
        title: Text(viewModel.alertTitle()),
        message: nil,
        dismissButton: .default(Text("RETRY")) {
          Task {
            await viewModel.retryLoadDogs()
          }
        })
    }
  }
}
```

To preview the result let's create a fake view model  
It will first simulate a .connectivity network error  
Second an .invalidData parsing error  
Third a success response with 3 dogs  

Start creating setting the preview
```swift 
struct DogsView_Previews: PreviewProvider {
  static var previews: some View {
    DogsView(viewModel: fakeViewModel()) // 1
  }
}
```
1. Needs to create this `fakeViewModel` that doesn't exist

Create the `fakeViewModel` 
```swift 
  static func fakeViewModel() -> DogsViewModel {
    let urlSessionSpy = URLSessionAsyncAwaitSpy( // 1
      results:
        [
          .failure(.connectivity),
          .failure(.invalidData),
          .success((validData(), validResponse())) // 2
        ]
    )
    let networkService = NetworkService(session: urlSessionSpy)
    let service = DogService(networkService: networkService)
    return DogsViewModel(dogService: service)
  }
```
1. Need to create the `URLSessionAsyncAwaitSpy` that will handle all fake results API
2. Need to create `validData()` and `validResponse()` that will simulate the fake valid response

First create the `URLSessionAsyncAwaitSpy`
```swift 
  private class URLSessionAsyncAwaitSpy: AsyncAwait {
    var results: [Result<(Data, URLResponse), NetworkService.Error>]

    internal init(results: [Result<(Data, URLResponse), NetworkService.Error>]) {
      self.results = results
    }

    func data(request: URLRequest) async throws -> (Data, URLResponse) {
      return try results.removeFirst().get() // 1
    }
  }
```
1. Use the `removeFirst()` function to return the result and will be ready for the next one

Second create `validData()` and `validResponse()`
```swift
  static func validData() -> Data {
    let dogsJSON =
    [
      "message":
        [
          "https://images.dog.ceo/breeds/setter-english/n02100735_10038.jpg",
          "https://images.dog.ceo/breeds/retriever-golden/n02099601_6331.jpg",
          "https://images.dog.ceo/breeds/komondor/n02105505_1657.jpg"
        ]
    ]

    do {
      return try JSONSerialization.data(withJSONObject: dogsJSON)
    } catch {
      assertionFailure("Expected to get a valid JSON, got error \(error) instead")
      return Data()
    }
  }

  static func validResponse() -> HTTPURLResponse {
    HTTPURLResponse(url: URL(string: "https://any-url.com")!, statusCode: 200, httpVersion: nil, headerFields: nil)!
  }
```

Run the preview  
First we failed as expected with a .connectivity error  
With the expected message create when TDD the view model
![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1665606474527/TgloNRdnG.png align="left")

Tap the RETRY button the and the .invalidData error appear
![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1665606562664/YcC1Bcppj.png align="left")

Tap the RETRY button and the success with valid data will be displayed
![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1665606632333/JYAS3AhXf.png align="left")

---
I hope you enjoy this TDD journey with async/await  
async/await can be tested using stub (inject result)  
Because it needs how it will complete before method execution  
Or you will always be block by `await`  
If never completes with success or error  

That's it !!  
