在 Swift 5.5 引入了 async await 這個功能,讓我們可以更方便地處理非同步的任務。async await 可以讓我們用同步的方式寫出非同步的程式碼,避免了回呼地獄(callback hell)和金字塔(pyramid of doom)的問題。async await 的基本用法是這樣的:
step1: 開出 APIClient 檔案與 APIClientTests
專案資料夾的結構可以長這樣
step2: 測試 APIClient
/// APIClient.swift
struct APIClient {
}
/// APIClientTests.swift
import XCTest
@testable import TwStockTools
final class APIClientTests: XCTestCase {
var sut: APIClient!
override func setUpWithError() throws {
sut = APIClient()
}
override func tearDownWithError() throws {
sut = nil
}
}
step3: 在 APIClient.swift 檔案裡,加上這段 URLSessionProtocol,並宣告這個 protocol 需有下面這個 func
/// APIClient.swift
protocol URLSessionProtocol {
func data(for request: URLRequest, delegate: URLSessionTaskDelegate?) async throws -> (Data, URLResponse)
}
step4: 讓 URLSession conform URLSessionProtocol
/// APIClient.swift
extension URLSession: URLSessionProtocol {}
step5: 在 Unit 上加上 Mock object URLSessionProtocolMock
class URLSessionProtocolMock: URLSessionProtocol {
var dataForDelegateReturnValue: (Data, URLResponse)?
var dataForDelegateRequest: URLRequest?
func data(for request: URLRequest, delegate: URLSessionTaskDelegate?) async throws -> (Data, URLResponse) {
dataForDelegateRequest = request
guard let dataForDelegateReturnValue = dataForDelegateReturnValue else {
fatalError()
}
return dataForDelegateReturnValue
}
}
step6: 在 APIClient.swift 使用 URLSessionProtocolMock()
/// APIClient.swift
/// 測試每日收盤價與月均價
/// https://openapi.twse.com.tw/v1/exchangeReport/STOCK_DAY_AVG_ALL
func testClosePriceAndMonthlyAvgPrice() async throws {
let url = try XCTUnwrap(URL(string: "https://openapi.twse.com.tw/v1/exchangeReport/STOCK_DAY_AVG_ALL"))
let urlSessionMock = URLSessionProtocolMock()
let expectedStockInfos = [StockDailyInfoElement(code: "0050", name: "元大台灣50", closingPrice: "123.55", monthlyAveragePrice: "125.16")]
urlSessionMock.dataForDelegateReturnValue = (try JSONEncoder().encode(expectedStockInfos), HTTPURLResponse(url: url, statusCode: 200, httpVersion: "HTTP/1.1", headerFields: nil)!)
sut.session = urlSessionMock
}
sut.session 現在會報錯,將 session 補上
step7: 將 APIClient 補上 session property 與 getStockClosPriceList()
struct APIClient {
lazy var session: URLSessionProtocol = URLSession.shared
func getStockClosPriceList() async throws -> [StockDailyInfoElement] {
return []
}
}
step8: 開始測 getStockClosPriceList 然後會得到 error。開始修改 client
// APIClient.swift
func getStockClosPriceList() async throws -> [StockDailyInfoElement] {
guard let url = URL(string: "https://openapi.twse.com.tw/v1/exchangeReport/STOCK_DAY_AVG_ALL") else {
return []
}
let request = URLRequest(url: url)
let (data, _) = try await session.data(for: request, delegate: nil)
let stocks = try JSONDecoder().decode([StockDailyInfoElement].self, from: data)
return stocks
}
step9: 再跑一次測試,可以看到測試全部通過,不過,這個 test 沒有處理到 error 的部分,下一篇來處理 Error