Masalah
Pindah ke dunia Swift dari ObjC / C ++, saya mengalami masalah ketika menulis unit test: kurangnya alat untuk membuat objek Mock.
Saat menulis kode terurai, kami sering menyembunyikan detail implementasi di belakang antarmuka (protokol). Juga sangat mudah untuk memeriksa fungsionalitas suatu objek secara terpisah dari yang lain, mengganti bagian-bagian komponennya dengan mokami.
Googling, saya menemukan beberapa kerangka kerja Swift Mocking di github. Tetapi tidak ada satu pun dari mereka yang tampak jelas dan jelas digunakan oleh saya (karena satu atau beberapa alasan):
- memiliki awan dependensi
- tidak ada sintaks yang intuitif
- pembuatan kode / penulisan manual dari sejumlah besar kode tambahan diperlukan
- tidak terintegrasi dengan XCTest (memungkinkan basah, tetapi tidak diuji)
- untuk memiliki banyak fitur dibuang ke sekelompok kerangka kerja (kebingungan tentang apa dan mengapa)
- harus digunakan dengan jumlah terbatas dari tipe yang telah ditentukan / bawaan
Situasi ini tidak menyenangkan bagi saya, dan selama sekitar satu tahun saya menggunakan solusi dan moka buatan sendiri.
Objek tiruan yang ditulis sendiri itu sederhana, tetapi itu
- berbeda setiap waktu (reinvent the wheel)
- atau sama setiap kali (copy-paste)
- tidak terlihat digunakan
Keputusan
C ++ gTest / gMock ( Google).
Mock- . , «» ( ) .
gMock, sMock (Swift Mock) gMock.
https://github.com/Alkenso/sMock
sMock:
- mock /
- expectations
- - mock-
- XCTest.framework
- zero-dependency
- pure Swift
()
sMock
- Mock , ( )
- Mock methodCall. "" .
Mocking synchronous method
:
import XCTest
import sMock
protocol HTTPClient {
func sendRequestSync(_ request: String) -> String
}
class MockHTTPClient: HTTPClient {
let sendRequestSyncCall = MockMethod<String, String>()
func sendRequestSync(_ request: String) -> String {
sendRequestSyncCall.call(request) ?? ""
}
}
struct Client {
let httpClient: HTTPClient
func retrieveRecordsSync() -> [String] {
let response = httpClient.sendRequestSync("{ action: 'retrieve_records' }")
return response.split(separator: ";").map(String.init)
}
}
class ExampleTests: XCTestCase {
func test_Example() {
let mock = MockHTTPClient()
let client = Client(httpClient: mock)
mock.sendRequestSyncCall
.expect("Request sent.")
.match("{ action: 'retrieve_records' }")
.willOnce(
.return("r1;r2;r3"))
let records = client.retrieveRecordsSync()
XCTAssertEqual(records, ["r1", "r2", "r3"])
}
}
Mocking synchronous method + mocking asynchonous callback
Mock callback'a, .
protocol HTTPClient {
func sendRequestSync(_ request: String) -> String
}
class MockHTTPClient: HTTPClient {
let sendRequestSyncCall = MockMethod<String, String>()
func sendRequestSync(_ request: String) -> String {
sendRequestSyncCall.call(request) ?? ""
}
}
struct Client {
let httpClient: HTTPClient
func retrieveRecordsAsync(completion: @escaping ([String]) -> Void) {
let response = httpClient.sendRequestSync("{ action: 'retrieve_records' }")
completion(response.split(separator: ";").map(String.init))
}
}
class ExampleTests: XCTestCase {
func test_Example() {
let mock = MockHTTPClient()
let client = Client(httpClient: mock)
mock.sendRequestSyncCall
.expect("Request sent.")
.match("{ action: 'retrieve_records' }")
.willOnce(
.return("r1;r2;r3"))
let completionCall = MockClosure<[String], Void>()
completionCall
.expect("Records retrieved.")
.match(["r1", "r2", "r3"])
.willOnce()
client.retrieveRecordsAsync(completion: completionCall.asClosure())
sMock.waitForExpectations()
}
}
Matchers, Actions, Argument capture
sMock (. ):
- Matchers β , .match: .any ( )
- Actions: expectation match'e .return, .
- : expectation .
- : , unexpectedCall
.
.
: , . .
, Swift .
, sMock . .
"testing with Swift"!
Try sMock on github (with SwiftPackageManager)