Getting started with TDD: Swift Unit Testing
Tags: ios, programming, swift
Published: 2021-10-28 06:35:53

The  [TDD (Test Driven Development)](https://en.wikipedia.org/wiki/Test-driven_development)  approach is important when it comes to testing small portions of business logic code in isolation.

In this blog, I'll cover how I would use TDD to build a clean architecture To-do SwiftUI application

I'll cover implementing the "Get To-dos" use case.

![Get Todos Use Case Flow Through Layers](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5ey9g6ppi0dkugmmscwu.png)

For this type of application, we need 2 types for application testing; UI Testing and Unit Testing. We'll be concentrating on the business logic, so we'll only be covering TDD unit testing. Before we write any production code, we'll write the test first.

The application will be structured in the following file and folder structure:

```
.
├── MyApp
    ├── Data
    │   ├── DataSource
    │   │   ├── TodoDataSource.swift
    │   │   └── DB
    │   │       ├── Entity
    │   │       │    └── TodoDBEntity.swift
    │   │       └── TodoDBDataSourceImpl.swift
    │   └── Repository
    │       └── TodoRepositoryImpl.swift
    ├── Domain
    │    ├── Model
    │    │   └── Todo.swift
    │    ├── Repository
    │    │   └── TodoRepository.swift
    │    └── UseCase
    │        └── GetTodos.swift
    └── Presentation
        ├── TodoViewModel.swift
        └── TodoListView.swift
└── MyAppTests
    ├── Data
    │   ├── DataSource
    │   │   └── DB
    │   │       └── TodoDBDataSourceImplTest.swift
    │   └── Repository
    │       └── TodoRepositoryImplTest.swift
    ├── Domain
    │    └── UseCase
    │        └── GetTodosTest.swift
    └── Presentation
        └── TodoViewModelTest.swift

```

We'd like to develop the "TodoViewModel" through the practice of TDD

#### A note on TDD
TDD is a software development process in which the unit test will be written first and after that the original code. In TDD, the design and development of the code are through unit tests.

In the traditional unit tests, the unit test is written after the original code is written. This is only for long-term maintainability. But in TDD the unit test is written first and code evolved through it.

#### Red Green Refactor

Red Green Refactor is an interesting concept in TDD. The stages are given below:

**Red** - First a failing unit test is created, and it results in red status

**Green** - We will modify the associated code to just make the unit test pass - resulting in green status

**Refactor** - Once the test is passing we can refactor the code so that the original implementation is done.

To get started with testing in your iOS SwiftUI project, we need to add a test target:


![Screenshot 2021-10-27 at 13.08.10.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1635332909190/6ST1cZ0nT.png)


![Screenshot 2021-10-27 at 13.04.56.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1635332809283/r74Tf26DD.png)

This now creates a test template:
To run the entire test suite click on the run button next to the class name or to run individual tests click the diamond button next to each test. Also note that each test needs to begin with the word "test"


![Screenshot 2021-10-27 at 13.12.19.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1635333144374/Nopqur21a.png)

Let's create the test and application file and folder structure. We'll just create the file without any production code.  All development code will be driven by test code.



![Screenshot 2021-10-27 at 13.35.43.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1635334559509/J67taId-3.png)

Let's start with our first test:


![Screenshot 2021-10-27 at 13.43.39.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1635335043055/7WZuRByXz.png)

*MyAppTests/Presentation/TodoViewModelTest.swift*

Of course, this test doesn't build and therefore fails because the file doesn't exist. 
We're in the RED Stage.

Let's write the minimum code to make the test pass. 

```swift
import Foundation

class TodoViewModel: ObservableObject {

}
```
If we run the test again, it passes - GREEN Stage


![Screenshot 2021-10-27 at 13.51.52.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1635335517862/S-LoqOLCA.png)


We also notice that the View Model takes a dependency so we'll have to change the test to include the getTodosUseCase


![Screenshot 2021-10-27 at 14.22.48.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1635337383119/LliTXBGHn.png)

We're back in RED stage

Let's update the view model

```swift
import Foundation

class TodoViewModel: ObservableObject {
    
    private var getTodosUseCase : GetTodos
    
    init(getTodosUseCase: GetTodos){
        self.getTodosUseCase = getTodosUseCase
    }

}
```


We need to create the protocol and mock implementation for GetTodosUseCase. While we are at it let's create the Todo Model


```swift
enum UseCaseError: Error{
    case dataSourceError, decodingError
}

protocol GetTodos {
    func execute() async -> Result<[Todo], UseCaseError>
}
```
<center><sub>*MyApp/Domain/UseCase/GetTodos.swift*</sub></center>


```swift
import Foundation

struct Todo: Identifiable {
    let id: Int
    let title: String
    let isCompleted: Bool
}
```
<center><sub>*MyApp/Domain/Model/Todo.swift*</sub></center>

Because we're testing and developing the view model, we don't need a GetTodos use case implementation at this stage, we will need to create a mock use case that implements the protocol

```swift
import Foundation
@testable import MyApp

class MockGetTodosUseCase: GetTodos{
    func execute() async -> Result<[Todo], UseCaseError> {
        Result.success([
            Todo(id: 1, title: "Mock Title One", isCompleted: true),
            Todo(id: 2, title: "Mock Title Two", isCompleted: false)
        ])
    }
}
```
<center><sub>*MyAppTests/Domain/UseCase/MockGetTodosUseCase.swift*</sub></center>

The test passes - GREEN stage

![Screenshot 2021-10-27 at 14.48.19.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1635338905220/A_s3dTWVR.png)

Let's add a few more tests to drive the development of the view model

---
#### testTodoViewModel_Should_Return_An_Empty_Todos_List

![Screenshot 2021-10-27 at 14.53.59.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1635339245571/FCGeZCJ8K.png)
```swift
import Foundation

class TodoViewModel: ObservableObject {
       
    private var getTodosUseCase : GetTodos
    
    init(getTodosUseCase: GetTodos){
        self.getTodosUseCase = getTodosUseCase
    }
    
    @Published
    var todos: [Todo] = []

}
```
![Screenshot 2021-10-27 at 14.58.13.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1635339497097/SgRO_NaML.png)

---
#### testTodoViewModel_Should_Return_2_Todos_When_getTodos_Is_Invoked


![Screenshot 2021-10-27 at 15.08.25.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1635340121789/7YodqHfSJ.png)

```swift
import Foundation

class TodoViewModel: ObservableObject {
       
    private var getTodosUseCase : GetTodos
    
    init(getTodosUseCase: GetTodos){
        self.getTodosUseCase = getTodosUseCase
    }
    
    @Published
    var todos: [Todo] = []
    
    func getTodos() async {
        
        let result = await self.getTodosUseCase.execute()
        switch result{
        case .success(let todos):
            self.todos = todos
        case .failure(_):
            self.todos = []
        
        }
    }
}
```

![Screenshot 2021-10-27 at 15.09.41.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1635340197034/_QmRt8lEx.png)

---
#### testTodoViewModel_Should_Display_Error_Message_When_getTodos_Results_In_Error

let's create a use case that results in an error

```swift
import Foundation
@testable import MyApp

class MockGetTodosErrorUseCase: GetTodos{
    func execute() async -> Result<[Todo], UseCaseError> {
        Result.failure(.dataSourceError)
    }
}
```
<center><sub>*MyAppTests/Domain/UseCase/MockGetTodosErrorUseCase.swift*</sub></center>




![Screenshot 2021-10-27 at 15.34.31.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1635342258714/QDTx78vPz.png)

```swift
import Foundation

class TodoViewModel: ObservableObject {
       
    private var getTodosUseCase : GetTodos
    
    init(getTodosUseCase: GetTodos){
        self.getTodosUseCase = getTodosUseCase
    }
    
    @Published
    var todos: [Todo] = []
    
    
    @Published
    var errorMessage = ""
    
    func getTodos() async {
        
        let result = await self.getTodosUseCase.execute()
        switch result{
        case .success(let todos):
            self.todos = todos
        case .failure(let error):
            self.todos = []
            
            if(error == UseCaseError.dataSourceError){
                errorMessage = "Error"
            }
            
            if(error == UseCaseError.decodingError){
                errorMessage = "Data Decoding Error"
            }
        }
    }
}
```


![Screenshot 2021-10-27 at 15.45.13.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1635342327426/l69hG9JHWd.png)

Our test is finally picking up that the error message is incorrect. We need to rename it from "Error" to "Data Source Error" and all the tests finally pass

```swift
     if(error == UseCaseError.dataSourceError){
                errorMessage = "Data Source Error"
            }
```

![Screenshot 2021-10-27 at 15.47.31.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1635342457190/C86CEo8TN.png)


Our code for the todo view model is finally done. We can use the same process for all other business logic in other layers.