在 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
