Swift中的HTTP模擬測試示例詳解
正文
我們已經(jīng)了解了單個方法如何為通過網(wǎng)絡(luò)加載請求提供基礎(chǔ)。
然而,網(wǎng)絡(luò)也是開發(fā)應(yīng)用程序時最大的失敗點(diǎn)之一,尤其是在單元測試方面。 當(dāng)我們編寫單元測試時,我們希望測試是可重復(fù)的:無論我們執(zhí)行多少次,我們應(yīng)該總是得到相同的結(jié)果。
如果我們的測試涉及實(shí)時網(wǎng)絡(luò)連接,我們無法保證這一點(diǎn)。 由于我們實(shí)際網(wǎng)絡(luò)請求失敗的所有原因,我們的單元測試也可能失敗。
因此,我們使用模擬對象來模擬網(wǎng)絡(luò)連接,但實(shí)際上提供了一個一致且可重復(fù)的外觀,我們可以通過它提供虛假數(shù)據(jù)。
由于我們已將網(wǎng)絡(luò)接口抽象為單個方法,因此模擬它非常簡單。
這是一個始終返回 200 OK 響應(yīng)的 HTTPLoading 實(shí)現(xiàn):
public class MockLoader: HTTPLoading {
public func load(request: HTTPRequest, completion: @escaping (HTTPResult) -> Void) {
let urlResponse = HTTPURLResponse(url: request.url!, statusCode: HTTPStatus(rawValue: 200), httpVersion: "1.1", headerFields: nil)!
let response = HTTPResponse(request: request, response: urlResponse, body: nil)
completion(.success(response))
}
}
我們可以在任何需要 HTTPLoading 值的地方提供 MockLoader 的實(shí)例,發(fā)送給它的任何請求都將導(dǎo)致 200 OK 響應(yīng),盡管主體為 nil。
當(dāng)我們使用模擬網(wǎng)絡(luò)連接編寫單元測試時,我們并不是在測試網(wǎng)絡(luò)代碼本身。 通過模擬網(wǎng)絡(luò)層,我們將網(wǎng)絡(luò)作為變量移除,這意味著網(wǎng)絡(luò)不是被測試的對象:單元測試檢查實(shí)驗(yàn)的變量。
StarWarsAPI 類
我們將使用我們在上一篇文章中刪除的 StarWarsAPI 類來說明這一原則:
public class StarWarsAPI {
private let loader: HTTPLoading
public init(loader: HTTPLoading = URLSession.shared) {
self.loader = loader
}
public func requestPeople(completion: @escaping (...) -> Void) {
var r = HTTPRequest()
r.host = "swapi.dev"
r.path = "/api/people"
loader.load(request: r) { result in
// TODO: interpret the result
completion(...)
}
}
}
該類的測試將驗(yàn)證其行為:我們要確保它在不同情況下的行為正確。 例如,我們要確保 requestPeople() 方法在收到 200 OK 響應(yīng)或 404 Not Found 響應(yīng)或 500 Internal Server Error 時行為正確。 我們使用 MockLoader 模擬這些場景。 這些測試將使我們有信心在不破壞現(xiàn)有功能的情況下改進(jìn) StarWarsAPI 的實(shí)現(xiàn)。
MockLoader
為了滿足這些需求,我們的 MockLoader 需要:
保證傳入的請求是我們在測試中期望的請求 為每個請求提供自定義響應(yīng) 我個人版本的 MockLoader 大致如下所示:
public class MockLoader: HTTPLoading {
// typealiases help make method signatures simpler
public typealias HTTPHandler = (HTTPResult) -> Void
public typealias MockHandler = (HTTPRequest, HTTPHandler) -> Void
private var nextHandlers = Array<MockHandler>()
public override func load(request: HTTPRequest, completion: @escaping HTTPHandler) {
if nextHandlers.isEmpty == false {
let next = nextHandlers.removeFirst()
next(request, completion)
} else {
let error = HTTPError(code: .cannotConnect, request: request)
completion(.failure(error))
}
}
@discardableResult
public func then(_ handler: @escaping MockHandler) -> Mock {
nextHandlers.append(handler)
return self
}
}
這個 MockLoader 允許我提供如何響應(yīng)連續(xù)請求的個性化實(shí)現(xiàn)。 例如:
func test_sequentialExecutions() {
let mock = MockLoader()
for i in 0 ..< 5 {
mock.then { request, handler in
XCTAssert(request.path, "/(i)")
handler(.success(...))
}
}
for i in 0 ..< 5 {
var r = HTTPRequest()
r.path = "/(i)"
mock.load(r) { result in
XCTAssertEqual(result.response?.statusCode, .ok)
}
}
}
如果我們在為 StarWarsAPI 類編寫測試時使用這個 MockLoader,它可能看起來像這樣(我省略了 XCTestExpectations,因?yàn)樗鼈兣c本次討論沒有直接關(guān)系):
class StarWarsAPITests: XCTestCase {
let mock = MockLoader()
lazy var api: StarWarsAPI = { StarWarsAPI(loader: mock) }()
func test_200_OK_WithValidBody() {
mock.then { request, handler in
XCTAssertEqual(request.path, "/api/people")
handler(.success(/* 200 OK with some valid JSON */))
}
api.requestPeople { ...
// assert that "StarWarsAPI" correctly decoded the response
}
}
func test_200_OK_WithInvalidBody() {
mock.then { request, handler in
XCTAssertEqual(request.path, "/api/people")
handler(.success(/* 200 OK but some mangled JSON */))
}
api.requestPeople { ...
// assert that "StarWarsAPI" correctly realized the response was bad JSON
}
}
func test_404() {
mock.then { request, handler in
XCTAssertEqual(request.path, "/api/people")
handler(.success(/* 404 Not Found */))
}
api.requestPeople { ...
// assert that "StarWarsAPI" correctly produced an error
}
}
func test_DroppedConnection() {
mock.then { request, handler in
XCTAssertEqual(request.path, "/api/people")
handler(.failure(/* HTTPError of some kind */))
}
api.requestPeople { ...
// assert that "StarWarsAPI" correctly produced an error
}
}
...
}
當(dāng)我們編寫這樣的測試時,我們將 StarWarsAPI 視為一個“黑匣子”:給定特定的輸入條件,它是否總是產(chǎn)生預(yù)期的輸出結(jié)果?
我們的 HTTPLoading 抽象使得交換網(wǎng)絡(luò)堆棧的實(shí)現(xiàn)成為一個簡單的改變。 我們所做的只是將 MockLoader 傳遞給初始化程序而不是 URLSession。 這里的關(guān)鍵是意識到,通過使我們的 StarWarsAPI 依賴于接口 (HTTPLoading) 而不是具體化 (URLSession),我們極大地增強(qiáng)了它的實(shí)用性并使其更易于單獨(dú)使用(和測試)。
這種對特定實(shí)現(xiàn)的行為定義的依賴將在我們實(shí)現(xiàn)框架的其余部分時很好地為我們服務(wù)。 在下一篇文章中,我們會將 HTTPLoading 更改為一個類并添加一個屬性,該屬性將為我們可以想象的幾乎所有可能的網(wǎng)絡(luò)行為提供基礎(chǔ)。
以上就是Swift中的HTTP模擬測試示例詳解的詳細(xì)內(nèi)容,更多關(guān)于Swift HTTP模擬測試的資料請關(guān)注腳本之家其它相關(guān)文章!
- Swift中Optional值的鏈?zhǔn)秸{(diào)用學(xué)習(xí)筆記
- Swift 中如何使用 Option Pattern 改善可選項(xiàng)的 API 設(shè)計(jì)
- Swift?Package?技巧及混編兼容問題詳解
- Swift重構(gòu)自定義空等運(yùn)算符 “??=” 實(shí)例
- Swift?重構(gòu)重載運(yùn)算符示例解析
- Swift HTTP加載請求Loading Requests教程
- Swift中的HTTP請求體Request Bodies使用示例詳解
- Swift中的可選項(xiàng)Optional解包方式實(shí)現(xiàn)原理
相關(guān)文章
Swift自動調(diào)整視圖布局AutoLayout和AutoresizingMask功能詳解
這篇文章主要為大家介紹了Swift自動調(diào)整視圖布局AutoLayout和AutoresizingMask功能及使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-06-06
Swift中defer關(guān)鍵字推遲執(zhí)行示例詳解
這篇文章主要給大家介紹了關(guān)于Swift中defer關(guān)鍵字推遲執(zhí)行的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2018-03-03
在Mac OS的終端中運(yùn)行Swift應(yīng)用的方法
這篇文章主要介紹了在Mac OS的終端中運(yùn)行Swift應(yīng)用的方法,依靠Xcode的REPL功能來實(shí)現(xiàn),需要的朋友可以參考下2015-07-07
SwiftUI 登錄界面布局實(shí)現(xiàn)示例詳解
這篇文章主要為大家介紹了SwiftUI 登錄界面布局實(shí)現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09
swiftui開發(fā)之padding默認(rèn)值設(shè)置詳解
這篇文章主要為大家介紹了swiftui開發(fā)之padding默認(rèn)值設(shè)置詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09
SwiftUI學(xué)習(xí)之state和Binding的區(qū)別淺析
這篇文章主要給大家介紹了關(guān)于SwiftUI學(xué)習(xí)之state和Binding區(qū)別的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03
Swift Extension擴(kuò)展得使用詳細(xì)介紹
在swift中,extension與Objective-C的category有點(diǎn)類似,但是extension比起category來說更加強(qiáng)大和靈活,它不僅可以擴(kuò)展某種類型或結(jié)構(gòu)體的方法,同時它還可以與protocol等結(jié)合使用,編寫出更加靈活和強(qiáng)大的代碼2022-09-09

