# SwiftUI - Test drive the logic inside a view using TDD

## Problem: How test behavior inside a SwiftUI view ?
### Solution: Extract all the logic in a ViewModel and test drive only the behaviour using the TDD process !

You have this view to integrate 
![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1653826763460/fPoQaiPKf.png align="center")

Specs for this design look like this:  
<pre>
- [ ] Display favorites/restaurants  
</pre>

But there is a problem with this design  
It's only takes into account the happy path  
But a lot of things can go wrong here  
What happen:
- On slow init restaurants (slow server response)?  
- If loading restaurants failed (server error)?  
- If the server return an empty response (no restaurants)?  
- On slow update for favorite status (slow server response) ?  
- If favorite status failed (server error)?  

After ask those questions to the team  
I have more infos to understand what I'm trying to build  
Here are the new specs:  
<pre>
[ ] On init restaurants/favorites are loading  
    [ ] If init succeed but no restaurants, display an image with a message  
     [ ] If init succeed favorites/restaurants are displayed  
     [ ] User can select favorites/restaurant to display details view  
     [ ] User can add a restaurant to favorites  
        [ ] Status is pending (UI loader is display)  
        [ ] If success delete from restaurants and add to favorites  
        [ ] If failed deliver an error  
    [ ] User can delete restaurant from favorites  
        [ ] Status is pending (UI loader is display)  
        [ ] If success delete from favorites and add to restaurants
        [ ] If failed deliver an error  
    [ ] If init failed (at least one request failed) an error message is deliver  
        [ ] User can retry if load failed  
</pre>

Knowing what we want to build  
Let's test drive this implementation using TDD  
Every of those specs must have a corresponding test

---
Start with the first one
<pre>
[ ] On init restaurants/favorites are loading  
</pre>

With this test:
```swift
  func test_load_deliversStatusIsPending() {
    let sut = RestaurantViewModel() // 1
    
    sut.load() // 2
    
    XCTAssertEqual(sut.status, .pending) // 3
  }
```
1. The `RestaurantViewModel` type not exist
2. The SUT doesn't have a `load()` function
3. The status `.pending` not exist 
 
Write some code to make the test pass
```swift
class RestaurantViewModel: ObservableObject {
  enum Status {
    case pending
  }
  
  @Published var status: Status = .pending
  
  func load() {
    status = .pending
  }
}
```

Test succeeded  
First requirement done  
<pre>
[✅] On init restaurants/favorites are loading  
</pre>

---
Now test the error case  
<pre>
    [ ] If init failed (at least one request failed) an error message is deliver  
        [ ] User can retry if load failed  
</pre>

Like to start with errors   
Don't have to worry about them later:  
```swift
  func test_load_deliversErrorOnLoadFailure() {
    let error = NSError(domain: "any error", code: 404)
    let errorDetails = ErrorDetails(title: "Request Failed", message: "Error 404", isRetry: true)
    let service = RestaurantService() // 1
    let sut = RestaurantViewModel(service: service)
    
    let exp = XCTestExpectation(description: "Expect load completion")
    
    sut.load {
      exp.fulfill()
    }
    
    service.completeWithError(error) // 2
    
    wait(for: [exp], timeout: 1)
    
    XCTAssertEqual(sut.errorDetails?.title, errorDetails.title, "Expect title update")
    XCTAssertEqual(sut.errorDetails?.message, errorDetails.message, "Expect message update")
    XCTAssertEqual(sut.errorDetails?.isRetry, errorDetails.isRetry, "Expect is retry update")
    XCTAssertTrue(sut.isPresentAlert, "Expect that setting the error details update this property")
  }
```
1. The `service` will be the futur object that handles API request
2. The `service` simulate a load completion with an error

Now fix the compiler error  
The `ErrorDetails` type not exist  
```swift 
struct ErrorDetails {
  let title: String
  let message: String
  let isRetry: Bool
}
```

The `RestaurantService` not exist  
It also needs functions called `completeWithError()` and `load()`  
```swift 
class RestaurantService {
  typealias LoadCompletions = (Result<(restaurants: [Restaurant],
                                       favorites: [Favorite]), Error>) -> Void
  
  var loadCompletions = [LoadCompletions]()
  
  func load(completion: @escaping LoadCompletions) {
    loadCompletions.append(completion) // 1
  }

  func completeWithError(_ error: Error, at index: Int = 0) {
    loadCompletions[index](.failure(error)) // 2
  }
}
```
1. Everytime the `load()` function is called, I accumulate the completion  
2. Access the completion and send a `.failure(error)` message  

Inject the `RestaurantService` in the `RestaurantViewModel`  
```swift 
class RestaurantViewModel: ObservableObject {
  ...
  let service: RestaurantService
  ...
  init(service: RestaurantService) {
    self.service = service
  }
  ...
}
```

Add a completion to the load function of the SUT (`RestaurantViewModel`)  
To access to the result when complete  
```swift 
  func load(completion: @escaping () -> Void) {
``` 

Add the `isPresentAlert` and `errorDetails` properties  
```swift 
class RestaurantViewModel: ObservableObject {
  ...
  @Published var isPresentAlert = false
  var errorDetails: ErrorDetails? {
    didSet { updateAlertVisibility() } // 1
  }
  ...
    
  // MARK: - Helpers
  
  private func updateAlertVisibility() {
    isPresentAlert = errorDetails == nil ? false : true
  }
}
```
1. The property `isPresentAlert` need to be update according to the `errorDetails` state   

Now make the test pass  
Updating `errorDetails` and `isPresentAlert` properties  
When loading failed  
```swift 
class RestaurantViewModel: ObservableObject {
  ...
  func load(completion: @escaping () -> Void) {
    status = .pending
    
    service.load { [weak self] result in
      switch result {
      case .failure(let error as NSError):
        self?.errorDetails = ErrorDetails(title: "Request Failed",
                                         message: "Error \(error.code)",
                                         isRetry: true)
      case .success:
        break
      }
      completion()
    }
  }
  ...
}
```

Test is passing  
Checkmark this requirement as done  
<pre>
    [✅] If init failed (at least one request failed) an error message is deliver  
        [✅] User can retry if load failed  
</pre>

---
Simplify the test setup  
Extract it to the `makeSUT()` function  
```swift 
  private func makeSUT() -> (sut: RestaurantViewModel, service: RestaurantService) {
    let service = RestaurantService()
    let sut = RestaurantViewModel(service: service)
    return (sut, service)
  }
```

Used like this in test  
```swift 
  func test_load_deliversErrorOnRequestFailure() {
    ...
    let (sut, service) = makeSUT()
    ...
}
``` 

---
Now the success loading with empty restaurants  
<pre>
  [ ] If init succeed but no restaurants, display an image with a message  
</pre>

Start writing a failing test  
```swift 
  func test_load_deliverEmptyMessageWhenCompleteWithEmptyRestaurantsAndFavorites() {
    let emptyRestaurants = [Restaurant]()
    let emptyFavorites = [Favorite]()
    let (sut, service) = makeSUT()

    let exp = XCTestExpectation(description: "Expect load completion")
    
    sut.load {
      exp.fulfill()
    }
    
    service.completeSuccessfullyWith(restaurants: emptyRestaurants,
                                     favorites: emptyFavorites) // 1
    
    wait(for: [exp], timeout: 1)
    
    XCTAssertEqual(sut.status, .empty) // 2
  }
```
1. The function `completeSuccessfullyWith` not exist  
2. The `.empty` case not exist

Add the `completeSuccessfullyWith` to the `service`  
```swift
  func completeSuccessfullyWith(restaurants: [Restaurant], favorites: [Favorite], at index: Int = 0) {
    loadCompletions[index](.success((restaurants: restaurants,
                                     favorites: favorites)))
  }
```

Add the `.empty` case to the status  
```swift 
  enum Status {
    case pending
    case empty
  }
```

The test failed  
*XCTAssertEqual failed: ("pending") is not equal to ("empty")*

Let's make it pass   
Update the SUT to handle the `.success` case  
```swift 
  func load(completion: @escaping () -> Void) {
    status = .pending
    
    service.load { [weak self] result in
      switch result {
      case .failure(let error as NSError):
        self?.errorDetails = ErrorDetails(title: "Request Failed",
                                         message: "Error \(error.code)",
                                         isRetry: true)
      case .success(let success):
        let isRestaurantEmpty = success.restaurants.isEmpty && success.favorites.isEmpty
        
        if isRestaurantEmpty {
          self?.status = .empty
        }
      }
      completion()
    }
  }
```

Test are passing   

---
Extract the logic for the empty restaurant tests  
To avoid repetition   
And improve test readability  
```swift
  private func expect(_ sut: RestaurantViewModel, toCompleteWith expectedStatus: RestaurantViewModel.Status, when action: () -> Void) {
    let exp = XCTestExpectation(description: "Expect load completion")
    
    sut.load {
      exp.fulfill()
    }
    
    action()
    
    wait(for: [exp], timeout: 1)
    
    XCTAssertEqual(sut.status, expectedStatus)
  }
```

Refactoring the test  
``` swift 
  func test_load_deliverEmptyMessageWhenCompleteWithEmptyRestaurantsAndFavorites() {
    let emptyRestaurants = [Restaurant]()
    let emptyFavorites = [Favorite]()
    let (sut, service) = makeSUT()

    expect(
      sut,
      toCompleteWith: .empty,
      when: {
        service.completeSuccessfullyWith(restaurants: emptyRestaurants,
                                         favorites: emptyFavorites)
      })
  }
```

---
Now test the happy path  
Restaurants and favorites are returned properly
<pre>
    [ ] If init succeed favorites/restaurants are displayed (both requests must be successful)  
</pre>

```swift 
  func test_load_deliversLoadedRestaurantsAndFavorites() {
    let restaurants = [kfc(), burgerKing()] // 1
    let favorites = [mcDonalds()] // 1
    let (sut, service) = makeSUT()
    
    expect(
      sut,
      toCompleteWith: .loaded(restaurants: restaurants,
                              favorites: favorites),
      when: {
        service.completeSuccessfullyWith(restaurants: restaurants,
                                         favorites: favorites)
      })
  }
```
1. Restaurants `kfc()`, `burgerKing()` and `mcDonalds()` not exist   
2. Status `.loaded(restaurants:favorites:)` not exist    

First create fake restaurants  
```swift 
func mcDonalds() -> Restaurant {
  .init(imageName: "mc-donalds",
        imageNameFavorite: "mc-donalds-favorite",
        waitingTime: "25-35",
        name: "McDonald's",
        rate: "4.0 Good (500+)",
        type: "American, Burgers")
}

func burgerKing() -> Restaurant {
  .init(imageName: "burger-king",
        imageNameFavorite: "burger-king-favorite",
        waitingTime: "25-35",
        name: "BURGER KING",
        rate: "4.3 Very good (500+)",
        type: "American, Burgers")
}

func kfc() -> Restaurant {
  .init(imageName: "kfc",
        imageNameFavorite: "kfc-favorite",
        waitingTime: "15-25",
        name: "KFC",
        rate: "4.1 Good (500+)",
        type: "Sandwichs, Burgers")
}

func carls() -> Restaurant {
  .init(imageName: "carls-jr",
        imageNameFavorite: "carls-jr-favorite",
        waitingTime: "15-25",
        name: "Carl's Jr",
        rate: "3.8 Good (61)",
        type: "Sandwichs, Burgers")
}
```

Second create the `loaded(restaurants:favorites)` status  
``` swift
enum Status {
    case pending
    case empty
    case loaded(restaurants: [Restaurant], favorites: [Favorite])
}
```

The compiler returns an another error  
In the assertion of the helper function  
*Global function 'XCTAssertEqual(_:_:_:file:line:)' requires that 'RestaurantViewModel.Status' conform to 'Equatable'*  

Let's conform to `Equatable`  
```swift 
  enum Status: Equatable {
    case pending
    case empty
    case loaded(restaurants: [Restaurant], favorites: [Favorite])
  }
```

Test failed  
*XCTAssertEqual failed: ("pending") is not equal to ("loaded(restaurants: [SwiftUITestDrivenViewTests.Restaurant(imageName: "kfc", imageNameFavorite: "kfc-favorite", waitingTime: "15-25", name: "KFC", rate: "4.1 Good (500+)", type: "Sandwichs, Burgers"), SwiftUITestDrivenViewTests.Restaurant(imageName: "burger-king", imageNameFavorite: "burger-king-favorite", waitingTime: "25-35", name: "BURGER KING", rate: "4.3 Very good (500+)", type: "American, Burgers")], favorites: [SwiftUITestDrivenViewTests.Restaurant(imageName: "mc-donalds", imageNameFavorite: "mc-donalds-favorite", waitingTime: "25-35", name: "McDonald\'s", rate: "4.0 Good (500+)", type: "American, Burgers")])")
*

Let's make it pass  
In the `.success` case add the `else` condition  
To handle non empty response  
```swift
      ...
      case .success(let success):
        let isRestaurantEmpty = success.restaurants.isEmpty && success.favorites.isEmpty
        
        if isRestaurantEmpty {
          self?.status = .empty
        } else {
          self?.status = .loaded(restaurants: success.restaurants,
                                favorites: success.favorites)
        }
        ...
```

Test is passing  
Checkmark this requirement as done  
<pre>
    [✅] If init succeed favorites/restaurants are displayed (both requests must be successful)  
</pre>

---
Now test the selection of a restaurant  
<pre>
    [ ] User can select favorites/restaurant to display details view  
</pre>

As usual start writing a test  
```swift 
  func test_selectRestaurant_pushSelectedRestaurantToDetailsView() {
    let selectedRestaurant = kfc()
    let (sut, _) = makeSUT()
    
    sut.updateSelected(with: selectedRestaurant) // 1
    
    XCTAssertEqual(sut.selectedRestaurant, selectedRestaurant) // 2
    XCTAssertTrue(sut.isPushToDetailsView) // 3
  }
```
1. The SUT don't have a function `updateSelected(with:)`  
2. The SUT don't have `selectedRestaurant` property  
3. The SUT don't have `isPushToDetailsView` property  

Add them to the SUT  
And also a private function `updateDetailViewVisibility()`  
That handle details view visibility  
```swift 
class RestaurantViewModel: ObservableObject {
  ...
  @Published var isPushToDetailsView = false
  private(set) var selectedRestaurant: Restaurant? {
    didSet { updateDetailViewVisibility() }
  }
  ...
  func updateSelected(with restaurant: Restaurant?) {
    selectedRestaurant = restaurant
  }
  ...
  // MARK: Helpers
  ...
  private func updateDetailViewVisibility() {
    isPushToDetailsView = selectedRestaurant == nil ? false : true
  }
```

Test is passing  
Checkmark this requirement as done  
<pre>
    [✅] User can select favorites/restaurant to display details view  
</pre>

---
Test the behavior of adding a restaurant to favorite  
<pre>
     [ ] User can add a restaurant to favorites  
        [ ] Status is pending (UI loader is display)  
        [ ] If success delete from restaurants and add to favorites  
        [ ] If failed deliver an error  
</pre>

Start with the error  
```swift
  func test_addToFavorites_deliversErrorOnRequestFailure() {
    let error = NSError(domain: "any error", code: 404)
    let errorDetails = ErrorDetails(title: "Request Failed", message: "Error 404", isRetry: false)
    let (sut, service) = makeSUT()
    let restaurant = kfc()
    
    let exp = XCTestExpectation(description: "Expect addToFavorites completion")
    
    sut.addToFavorites(restaurant) { // 1
      exp.fulfill()
    }
    
    service.completeAddFavoriteWithError(error) // 2
    
    wait(for: [exp], timeout: 1)
    
    XCTAssertEqual(sut.errorDetails?.title, errorDetails.title)
    XCTAssertEqual(sut.errorDetails?.message, errorDetails.message)
    XCTAssertEqual(sut.errorDetails?.isRetry, errorDetails.isRetry)
    XCTAssertTrue(sut.isPresentAlert, "Expect that setting the error details update this property")
  } 
```
1. The SUT don't have the `addToFavorites(restaurant:)` function  
2. The service don't have the `completeAddFavoriteWithError(error:)` function  

Add `addToFavorites(restaurant:)` to the SUT  
```swift 
  func addToFavorites(_ restaurant: Restaurant, completion: @escaping () -> Void) {

  }
```
 
Add `completeAddFavoriteWithError(error:)` to the `service`  
``` swift 
class RestaurantService {
  ...
  typealias FavoriteResult = Result<Restaurant, Error>
  typealias FavoriteCompletions = (FavoriteResult) -> Void
  ...
  var addFavoriteCompletions = [FavoriteCompletions]()
  ...
  func addFavorite(_ restaurant: Restaurant, completion: @escaping FavoriteCompletions) {
    addFavoriteCompletions.append(completion)
  }
  ...
  func completeAddFavoriteWithError(_ error: Error, at index: Int = 0) {
    addFavoriteCompletions[index](.failure(error))
  }
}
```

Run tests  
There is a crash  
*Thread 1: Fatal error: Index out of range*  
```swift 
  func completeAddFavoriteWithError(_ error: Error, at index: Int = 0) {
    addFavoriteCompletions[index](.failure(error))
  }
```
The `addFavoriteCompletions` property is empty   

When we check `RestaurantViewModel`   
The `addToFavorites(restaurant:completion:)` does nothing  
```swift
  func addToFavorites(_ restaurant: Restaurant, completion: @escaping () -> Void) {

  }
```

Ok let's fix this   
```swift 
  func addToFavorites(_ restaurant: Restaurant, completion: @escaping () -> Void) {
    service.addFavorite(restaurant) { result in
      switch result {
      case .success:
        break
      case let .failure(error as NSError):
        self.errorDetails = ErrorDetails(title: "Request Failed",
                                         message: "Error \(error.code)",
                                         isRetry: false)
      }
      completion()
    }
  }
```

Test is passing  
Checkmark this requirement as done  
<pre>
    [ ] User can add restaurant to favorites  
        [ ] Status is pending (UI loader is display)  
        [ ] If success add to favorites and delete from restaurant  
        [✅] If failed deliver an error  
</pre>

---
Now test the add favorite restaurant success case  
<pre>
        [ ] If success add to favorites and delete from restaurant  
</pre>

Start with a test  
```swift
  func test_addToFavorites_addToFavoritesDeleteFromRestaurantsOnSuccess() {
    let restaurant = kfc()
    let (sut, service) = makeSUT()
    sut.status = .loaded(restaurants: [restaurant], favorites: [])
    let exp = XCTestExpectation(description: "Expect load completion")
    
    sut.addToFavorites(restaurant) {
      exp.fulfill()
    }
    
    service.completeAddFavoriteSuccessfullyWith(restaurant) // 1
    
    wait(for: [exp], timeout: 1)
    
    XCTAssertEqual(sut.status, .loaded(restaurants: [], favorites: [restaurant]))
  }
```
1. The SUT don't have the `completeAddFavoriteSuccessfullyWith(restaurant:)` function  

Add the `completeAddFavoriteSuccessfullyWith(restaurant:)` to the `RestaurantService`  
```swift 
  func completeAddFavoriteSuccessfullyWith(_ restaurant: Restaurant, at index: Int = 0) {
    addFavoriteCompletions[index](.success(restaurant))
  }
```

Test failed  
*XCTAssertEqual failed: ("loaded(restaurants: [SwiftUITestDrivenViewTests.Restaurant(imageName: "kfc", imageNameFavorite: "kfc-favorite", waitingTime: "15-25", name: "KFC", rate: "4.1 Good (500+)", type: "Sandwichs, Burgers")], favorites: [])") is not equal to ("loaded(restaurants: [], favorites: [SwiftUITestDrivenViewTests.Restaurant(imageName: "kfc", imageNameFavorite: "kfc-favorite", waitingTime: "15-25", name: "KFC", rate: "4.1 Good (500+)", type: "Sandwichs, Burgers")])")
*

The restaurant is not added to the favorite  
Let's fix this handling the success case   
```swift
  func addToFavorites(_ restaurant: Restaurant, completion: @escaping () -> Void) {
    service.addFavorite(restaurant) { result in
      switch result {
      case .success:
        self.updateStatusAddingFavorite(restaurant)
      case let .failure(error as NSError):
        self.errorDetails = ErrorDetails(title: "Request Failed",
                                         message: "Error \(error.code)",
                                         isRetry: false)
      }
      completion()
    }
  }
  ...
  // MARK: - Helpers
  ...
  private func updateStatusAddingFavorite(_ restaurant: Restaurant) {
    if case var .loaded(restaurants: restaurants, favorites: favorites) = self.status {
      favorites.append(restaurant)
      restaurants.removeAll(where: { $0 == restaurant })
      self.status = .loaded(restaurants: restaurants,
                            favorites: favorites)
    }
  }
``` 

Test is passing  
Checkmark this requirement as done  
<pre>
    [ ] User can add restaurant to favorites
        [ ] Status is pending (UI loader is display)
        [✅] If success add to favorites and delete from restaurant
        [✅] If failed deliver an error
</pre>

---
Now the pending status for a selected restaurant  
<pre>
        [ ] Status is pending (UI loader is display)  
</pre>

```swift 
func test_isPending_updatePendingStatusOnFavoriteApiRequest() {
    let restaurant = kfc()
    let (sut, service) = makeSUT()
    sut.status = .loaded(restaurants: [restaurant], favorites: [])
    let exp = XCTestExpectation(description: "Expect delete from favorite to complete")
    
    XCTAssertFalse(sut.isPending(for: restaurant), "Expect that restaurant is not pending on init") // 1
    
    sut.addToFavorites(restaurant) {
      exp.fulfill()
    }
    
    XCTAssertTrue(sut.isPending(for: restaurant), "Expect that restaurant is pending because of the running api request") // 1
    
    service.completeAddFavoriteSuccessfullyWith(restaurant)
    
    wait(for: [exp], timeout: 1)
    
    XCTAssertFalse(sut.isPending(for: restaurant), "Expect that restaurant is not pending because the api request complete") // 1
  }
```
1. The SUT don't have the `isPending(for:)` function  

Add `isPending(for:)` to the SUT  
```swift 
class RestaurantViewModel: ObservableObject {
  ...
  @Published private(set) var pendingRestaurants = [Restaurant]()
  ... 
  func isPending(for restaurant: Restaurant) -> Bool {
    pendingRestaurants.contains(restaurant)
  }
  ...
}
```

Test failed to the true assertion  
```swift 
    XCTAssertTrue(sut.isPending(for: restaurant), "Expect that restaurant is pending because of the running api request")
```

Let's make it pass   
```swift 
class RestaurantViewModel: ObservableObject {
  ...
  func addToFavorites(_ restaurant: Restaurant, completion: @escaping () -> Void) {
    addPending(restaurant)
    
    service.addFavorite(restaurant) { result in
      self.deletePending(restaurant)
      ...
    }
  }    
  ...    
  // MARK: - Helpers
  ...
  private func addPending(_ restaurant: Restaurant) {
    pendingRestaurants.append(restaurant)
  }
  
  private func deletePending(_ restaurant: Restaurant) {
    pendingRestaurants.removeAll(where: { $0 == restaurant })
  }
}
```

Test is passing  
Checkmark this requirement as done  
<pre>
    [✅] User can add restaurant to favorites  
        [✅] Status is pending (UI loader is display)  
        [✅] If success add to favorites and delete from restaurant  
        [✅] If failed deliver an error  
</pre>

---
Test for favorite deletion  
<pre>
    [ ] User can delete restaurant from favorites  
        [ ] Status is pending (UI loader is display)  
        [ ] If success delete from favorites and add to restaurants
        [ ] If failed deliver an error  
</pre>

I Leave you thoses case as an exercice  
Because it's the same as addition  

---
All behaviours are now tested  
<pre>
[✅] On init restaurants/favorites are loading  
    [✅] If init succeed but no restaurants, display an image with a message  
    [✅] If init succeed favorites/restaurants are displayed  
    [✅] User can select favorites/restaurant to display details view  
     [✅] User can add a restaurant to favorites  
        [✅] Status is pending (UI loader is display)  
        [✅] If success delete from restaurants and add to favorites  
        [✅] If failed deliver an error  
    [✅] User can delete restaurant from favorites  
        [✅] Status is pending (UI loader is display)  
        [✅] If success delete from favorites and add to restaurants
        [✅] If failed deliver an error  
    [✅] If init failed (at least one request failed) an error message is deliver  
        [✅] User can retry if load failed  
</pre>

We can now extract the production code from the test code   
In a `RestaurantService` as a `protocol`  
```swift 
protocol RestaurantService {
  typealias LoadCompletions = (Result<(restaurants: [Restaurant],
                                       favorites: [Favorite]), Error>) -> Void
  typealias FavoriteResult = Result<Restaurant, Error>
  typealias FavoriteCompletions = (FavoriteResult) -> Void
  
  func load(completion: @escaping LoadCompletions)
  func addFavorite(_ restaurant: Restaurant, completion: @escaping FavoriteCompletions)
  func deleteFavorite(_ restaurant: Restaurant, completion: @escaping FavoriteCompletions)
}
```

The `RestaurantService` class  
Now conforms to the protocol  
And becomes `RestaurantServiceSpy`   
`RestaurantServiceSpy` is now a private class   
In the Helpers section test  
```swift 
private  class RestaurantServiceSpy: RestaurantService {
    var loadCompletions = [LoadCompletions]()
    var addFavoriteCompletions = [FavoriteCompletions]()
    var deleteFavoriteCompletions = [FavoriteCompletions]()

    func load(completion: @escaping LoadCompletions) {
      loadCompletions.append(completion)
    }

    func addFavorite(_ restaurant: Restaurant, completion: @escaping FavoriteCompletions) {
      addFavoriteCompletions.append(completion)
    }

    func deleteFavorite(_ restaurant: Restaurant, completion: @escaping FavoriteCompletions) {
      deleteFavoriteCompletions.append(completion)
    }

    func completeWithError(_ error: Error, at index: Int = 0) {
      loadCompletions[index](.failure(error))
    }

    func completeSuccessfullyWith(restaurants: [Restaurant], favorites: [Favorite], at index: Int = 0) {
      loadCompletions[index](.success((restaurants: restaurants,
                                       favorites: favorites)))
    }

    func completeAddFavoriteWithError(_ error: Error, at index: Int = 0) {
      addFavoriteCompletions[index](.failure(error))
    }

    func completeAddFavoriteSuccessfullyWith(_ restaurant: Restaurant, at index: Int = 0) {
      addFavoriteCompletions[index](.success(restaurant))
    }

    func completeDeleteFavoriteWithError(_ error: Error, at index: Int = 0) {
      deleteFavoriteCompletions[index](.failure(error))
    }

    func completeDeleteFavoriteSuccessfullyWith(_ restaurant: Restaurant, at index: Int = 0) {
      deleteFavoriteCompletions[index](.success(restaurant))
    }
  }
```

---
I already create all SwiftUI views required for this app  
Those views of course called this tested view model

---
Now create a fake service   
That simulate failed and succeed request   
Here is the scenario that I want   
1. Load is a failure  
2. The user retry   
3. Load is a success  
4. Restaurants are displayed  
5. Add/delete a favorite always failed the first time  
6. After Add/delete a favorite always succeeded   
7. User can view a details view of a restaurant  

Let's create fake service for empty restaurants
```swift 
class FakeRestaurantService: RestaurantService {
  
  private var loadCallCount = 0
  private var addFavoriteCallCount = 0
  private var deleteFavoriteCallCount = 0

  func load(completion: @escaping LoadCompletions) {
    loadCallCount += 1
    print("loadCallCount: \(loadCallCount)")
    if loadCallCount < 2 {
      let error = NSError(domain: "any", code: 404)
      DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(1000)) {
        completion(.failure(error))
      }
    } else {
      DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(2000)) {
        completion(.success((restaurants: [mcDonalds(), kfc(), carls()],
                             favorites: [burgerKing()])))
      }
    }
  }
  
  func addFavorite(_ restaurant: Restaurant, completion: @escaping FavoriteCompletions) {
    addFavoriteCallCount += 1
    
    if addFavoriteCallCount < 2 {
      let error = NSError(domain: "any", code: 400)
      DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(1000)) {
        completion(.failure(error))
      }
    } else {
      DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(1000)) {
        completion(.success(restaurant))
      }
    }
  }
  
  func deleteFavorite(_ restaurant: Restaurant, completion: @escaping FavoriteCompletions) {
    deleteFavoriteCallCount += 1
    
    if deleteFavoriteCallCount < 2 {
      let error = NSError(domain: "any", code: 404)
      DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(1000)) {
        completion(.failure(error))
      }
    } else {
      DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(1000)) {
        completion(.success(restaurant))
      }
    }
  }
}
``` 

Use this fake service in the live preview   
```swift
struct RestaurantsView_Previews: PreviewProvider {
  static var previews: some View {
    RestaurantsView(title: "Restaurants",
                    viewModel: .init(service: FakeRestaurantService()))
  }
}
```

Let see it in action in the live preview
![Simulator Screen Recording - iPhone 11 - 2022-05-29 at 13.29.15.gif](https://cdn.hashnode.com/res/hashnode/image/upload/v1653824965728/3e0FLiZk5.gif align="center")

---
Cool we can simulate the behaviour of our use case   
Without API request   
No internet connection  
Even without running a single time the app in the simulator  
That boost our productivity a lot  

---
Create also a fake service for empty restaurant  
To check if it also works properly  
```swift 
class FakeEmptyRestaurantService: RestaurantService {
  func load(completion: @escaping LoadCompletions) {
    DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(2000)) {
      completion(.success((restaurants: [],
                           favorites: [])))
    }
  }
  
  func addFavorite(_ restaurant: Restaurant, completion: @escaping FavoriteCompletions) {}
  
  func deleteFavorite(_ restaurant: Restaurant, completion: @escaping FavoriteCompletions) {}
}
```

Use it in the live preview   
```swift
struct RestaurantsView_Previews: PreviewProvider {
  static var previews: some View {
    RestaurantsView(title: "Restaurants",
                    viewModel: .init(service: FakeRestaurantService()))
  }
}
```

It's working pretty well   
![Simulator Screen Recording - iPhone 11 - 2022-05-29 at 14.09.46.gif](https://cdn.hashnode.com/res/hashnode/image/upload/v1653826200292/Ar_tPg_G7.gif align="center")

---
Thanks to the power of TTD  
We only take care of the expected behaviour  
And leave implementation details away   
Avoid coupling our test with frameworks are other details  
Making our tests even more powerful  
Avoid future breaking changes   

That's it !!  



