问题
从ObjC / C ++进入Swift世界,编写单元测试时遇到一个问题:缺少用于创建Mock对象的工具。
在编写分解代码时,我们通常将实现细节隐藏在接口(协议)的后面。将对象的组成部分替换为mokami,也非常方便地与其他对象分开检查对象的功能。
谷歌搜索,我在github上找到了几个Swift Mocking框架。但是对我来说,它们在使用中都没有一个清晰明了(出于一个或几个原因):
- 有依赖性云
- 没有直观的语法
- 代码生成/需要大量辅助代码的手动编写
- 未与XCTest集成(允许潮湿,但未经测试)
- 将许多功能转储到一个框架中(混淆了什么和原因)
- 应该与有限数量的预定义/内置类型一起使用
这种情况对我来说是不愉快的,并且大约一年来,我使用了变通办法和自制的Mokas。
自写的模拟对象很简单,但是它们
- 每次都不同(重新发明轮子)
- 或每次相同(复制粘贴)
- 使用不明显
决断
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)