Edited at

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


概要

iOSエンジニアの通信処理の練習用にこんなAPIを作成しました。

herokuで無料枠でデプロイしていますのでお金はかからないはずです。

その代わりにスリープモードなのでアクセスしてもレスポンスが遅いので、2,3回アクセスして起こしてもらう必要があります。

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

スクリーンショット 2018-12-30 20.48.03.png

{"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

[{"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

{"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

{"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にしました。

今回は全てをデコードするのではなくidfirstlastをそれぞれデコードする。


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

スクリーンショット 2018-12-30 20.34.59.png

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

スクリーンショット 2018-12-30 20.35.45.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()を呼び出し場合

[TodoApp.Task(id: 1, first: "Taro", last: "Tanaka"), TodoApp.Task(id: 2, first: "Hanako", last: "Yamada")]

requestFirst()を呼び出し場合

MobileGestalt.c:890: MGIsDeviceOneOfType is not supported on this platform.

Task(id: 1, first: "Taro", last: "Tanaka")

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

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


ModelクラスのUnitTestを作成する

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

スクリーンショット 2018-12-30 21.48.21.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でデコードがしっかりできていることがわかりました。