SwiftのAlamofire+CodableでAPIクライアントの作成からUnitTestまでを実装する

# 概要

iOSエンジニアの通信処理の練習用にこんなAPIを作成しました。
herokuで無料枠でデプロイしていますのでお金はかからないはずです。
その代わりにスリープモードなのでアクセスしてもレスポンスが遅いので、2,3回アクセスして起こしてもらう必要があります。

https://todo-practice-app.herokuapp.com/

<img width="433" alt="スクリーンショット 2018-12-30 20.48.03.png" src="https://qiita-image-store.s3.amazonaws.com/0/44392/d73dca0d-df78-ec75-cde0-ee4d3ecb20c2.png">


```terminal
{"items":[{"id":1,"first_name":"Taro","last_name":"Tanaka","title":"first_data","create_at":"2018-12-30T15:03:01.012345"},{"id":2,"first_name":"Hanako","last_name":"Yamada","title":"second-data","create_at":"2018-12-31T18:11:01.012345"}]}
```

https://todo-practice-app.herokuapp.com/items

```terminal
[{"id":1,"first_name":"Taro","last_name":"Tanaka","title":"first_data","create_at":"2018-12-30T15:03:01.012345"},{"id":2,"first_name":"Hanako","last_name":"Yamada","title":"second-data","create_at":"2018-12-31T18:11:01.012345"}]

```

https://todo-practice-app.herokuapp.com/first

```terminal
{"id":1,"first_name":"Taro","last_name":"Tanaka","title":"first_data","create_at":"2018-12-30T15:03:01.012345"}
```


https://todo-practice-app.herokuapp.com/second

```terminal
{"id":2,"first_name":"Hanako","last_name":"Yamada","title":"second-data","create_at":"2018-12-31T18:11:01.012345"}
```

上記のURLに接続するとJSONが出力されます。  
つまり、こちらがAPIとなります。

これらをそれぞれネイティブ側(iOS)でパースしてみましょう。

## 環境

| ライブラリ・環境       |       バージョン |
|:----------------:|:-----------------:|
| Xcode             |              10.1 |
| Swift           |            4.2 |
| Alamofire             |              4.8.0 |

## モデルクラスの設計と実装

今回はモデルクラスの命名を`Task`にしました。
今回は全てをデコードするのではなく`id`と`first`と`last`をそれぞれデコードする。

```Task.swift
struct Task: Codable {
    let id: Int
    let first: String
    let last: String
    
    enum CodingKeys: String, CodingKey {
        case id
        case first = "first_name"
        case last = "last_name"
    }
}
```

https://todo-practice-app.herokuapp.com/easy/XXX

を使うとパラメータと変数名が一致するため`CodingKeys`が必要なくなります。
こちらは私なりの配慮です。

https://todo-practice-app.herokuapp.com/easy/items

<img width="553" alt="スクリーンショット 2018-12-30 20.34.59.png" src="https://qiita-image-store.s3.amazonaws.com/0/44392/da2782ba-dce1-2c4e-3188-c345320ec01c.png">

https://todo-practice-app.herokuapp.com/easy/first

<img width="628" alt="スクリーンショット 2018-12-30 20.35.45.png" src="https://qiita-image-store.s3.amazonaws.com/0/44392/04c06125-1adb-839f-7ce3-d8a4b93510a1.png">

easy版を使う場合

```Task.swift
struct Task: Codable {
    let id: Int
    let first: String
    let last: String
}
```

以上でモデルクラスの作成が完了します。  

## Controller側でAPIを呼び出してデコードする

```ViewController.swift
import UIKit
import Alamofire

class ViewController: UIViewController {
    
    private var items: [Task]?

    override func viewDidLoad() {
        super.viewDidLoad()
        requestFirst()
        requestItems()
    }
    
    private func requestFirst() {
        Alamofire.request("https://todo-practice-app.herokuapp.com/first").response { response in
            guard let data = response.data else {
                return
            }
            let decoder = JSONDecoder()
            do {
                let task: Task = try decoder.decode(Task.self, from: data)
                print(task)
            } catch {
                print(error)
            }
        }
    }
    
    private func requestItems() {
        Alamofire.request("https://todo-practice-app.herokuapp.com/items").response { response in
            guard let data = response.data else {
                return
            }
            let decoder = JSONDecoder()
            do {
                let tasks: [Task] = try decoder.decode([Task].self, from: data)
                print(tasks)
            } catch {
                print(error)
            }
        }
    }
}
```

`requestItems()`を呼び出し場合

```terminal
[TodoApp.Task(id: 1, first: "Taro", last: "Tanaka"), TodoApp.Task(id: 2, first: "Hanako", last: "Yamada")]
```
`requestFirst()`を呼び出し場合

```terminal
MobileGestalt.c:890: MGIsDeviceOneOfType is not supported on this platform.
Task(id: 1, first: "Taro", last: "Tanaka")
```

さて、ここまではよくQiitaで見かける記事であります。

次にこれのModelのUnitTestを作ります。

# ModelクラスのUnitTestを作成する

プロジェクトファイルの構成は下記の通りに実装します。

<img width="269" alt="スクリーンショット 2018-12-30 21.48.21.png" src="https://qiita-image-store.s3.amazonaws.com/0/44392/95fe1eb9-a50d-589c-34ab-eecdc5eb5196.png">

ResourcesフォルダにJSONのオブジェクトを入れる形式が好みです。

```one_task.json
{
    "id": 1,
    "first_name": "Taro",
    "last_name": "Tanaka",
    "title": "first_data",
    "create_at": "2018-12-30T15:03:01.012345"
}
```

テストを書きます。

```TestTask.swift
import XCTest
@testable import TodoApp

class TestTask: XCTestCase {
    
    func test_Task()
    {
        guard let jsonObject = createDataObject() else {
            XCTFail("Dataの生成に失敗")
            return
        }
            let decoder = JSONDecoder()
        guard let task = try? decoder.decode(Task.self, from: jsonObject) else {
            XCTFail("Taskの生成に失敗")
            return
        }
        XCTAssertEqual(task.id, 1)
        XCTAssertEqual(task.first, "Taro")
        XCTAssertEqual(task.last, "Tanaka")
    }
    
    private func createDataObject() -> Data?
    {
        let testBundle = Bundle(for: type(of: self))
        let path = testBundle.url(forResource: "one_task", withExtension: "json")
        let data = try? Data(contentsOf: path!, options: .uncached)
        return data
    }
}
```

あとはユニットテストを実行して`Success`が表示されたら完了です。  
これで無事にMockから`JSONDecoder`でCodableでデコードがしっかりできていることがわかりました。