Problema
Passando para o mundo Swift do ObjC / C ++, encontrei um problema ao escrever testes de unidade: a falta de ferramentas para criar objetos Mock.
Ao escrever código decomposto, geralmente ocultamos detalhes de implementação por trás de interfaces (protocolos). Também é muito conveniente verificar a funcionalidade de um objeto separadamente dos outros, substituindo seus componentes por mokami.
Pesquisando, encontrei várias estruturas do Swift Mocking no github. Mas nenhum deles me pareceu claro e óbvio em uso (por uma ou várias razões):
- tem uma nuvem de dependências
- nenhuma sintaxe intuitiva
- é necessária geração de código / gravação manual de uma grande quantidade de código auxiliar
- não integrado ao XCTest (permite a umidade, mas não é testado)
- ter muitos recursos despejados em um monte de uma estrutura (confusão do que e por quê)
- deve ser usado com um número limitado de tipos predefinidos / embutidos
Essa situação foi desagradável para mim e, por cerca de um ano, usei soluções alternativas e mokas feitas por você.
Objetos simulados auto-escritos são simples, mas eles
- sempre diferente (reinventar a roda)
- ou o mesmo sempre (copiar e colar)
- não óbvio para usar
Decisão
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)