Mocker 是一个用 Swift 编写的库,它可以使用自定义的 URLProtocol
来模拟数据请求。
离线运行所有数据请求单元测试 🎉
URLSession
配合使用Alamofire
为 Mocker
编写了单元测试,可以帮助您了解它的工作原理。
在您注册您的第一个 Mock
之后,mocker 将自动为默认的 URL 加载系统(例如 URLSession.shared
)激活。
要使其与您的自定义 URLSession
一起使用,需要注册 MockingURLProtocol
let configuration = URLSessionConfiguration.default
configuration.protocolClasses = [MockingURLProtocol.self]
let urlSession = URLSession(configuration: configuration)
与在自定义 URLSession
上注册非常相似。
let configuration = URLSessionConfiguration.af.default
configuration.protocolClasses = [MockingURLProtocol.self]
let sessionManager = Alamofire.Session(configuration: configuration)
建议创建一个包含所有可访问的模拟数据的类。 可以在此项目的单元测试中找到一个示例
public final class MockedData {
public static let botAvatarImageResponseHead: Data = try! Data(contentsOf: Bundle(for: MockedData.self).url(forResource: "Resources/Responses/bot-avatar-image-head", withExtension: "data")!)
public static let botAvatarImageFileUrl: URL = Bundle(for: MockedData.self).url(forResource: "wetransfer_bot_avater", withExtension: "png")!
public static let exampleJSON: URL = Bundle(for: MockedData.self).url(forResource: "Resources/JSON Files/example", withExtension: "json")!
}
let originalURL = URL(string: "https://www.wetransfer.com/example.json")!
let mock = Mock(url: originalURL, contentType: .json, statusCode: 200, data: [
.get : try! Data(contentsOf: MockedData.exampleJSON) // Data containing the JSON response
])
mock.register()
URLSession.shared.dataTask(with: originalURL) { (data, response, error) in
guard let data = data, let jsonDictionary = (try? JSONSerialization.jsonObject(with: data, options: [])) as? [String: Any] else {
return
}
// jsonDictionary contains your JSON sample file data
// ..
}.resume()
let originalURL = URL(string: "https://www.wetransfer.com/api/foobar")!
var request = URLRequest(url: originalURL)
request.httpMethod = "PUT"
let mock = Mock(request: request, statusCode: 204)
mock.register()
URLSession.shared.dataTask(with: originalURL) { (data, response, error) in
// ....
}.resume()
某些 URL(例如身份验证 URL)在查询中包含时间戳或 UUID。 要模拟这些,您可以忽略特定 URL 的查询
/// Would transform to "https://www.example.com/api/authentication" for example.
let originalURL = URL(string: "https://www.example.com/api/authentication?oauth_timestamp=151817037")!
let mock = Mock(url: originalURL, ignoreQuery: true, contentType: .json, statusCode: 200, data: [
.get : try! Data(contentsOf: MockedData.exampleJSON) // Data containing the JSON response
])
mock.register()
URLSession.shared.dataTask(with: originalURL) { (data, response, error) in
guard let data = data, let jsonDictionary = (try? JSONSerialization.jsonObject(with: data, options: [])) as? [String: Any] else {
return
}
// jsonDictionary contains your JSON sample file data
// ..
}.resume()
let imageURL = URL(string: "https://www.wetransfer.com/sample-image.png")!
Mock(fileExtensions: "png", contentType: .imagePNG, statusCode: 200, data: [
.get: try! Data(contentsOf: MockedData.botAvatarImageFileUrl)
]).register()
URLSession.shared.dataTask(with: imageURL) { (data, response, error) in
let botAvatarImage: UIImage = UIImage(data: data!)! // This is the image from your resources.
}.resume()
let exampleURL = URL(string: "https://www.wetransfer.com/api/endpoint")!
Mock(url: exampleURL, contentType: .json, statusCode: 200, data: [
.head: try! Data(contentsOf: MockedData.headResponse),
.get: try! Data(contentsOf: MockedData.exampleJSON)
]).register()
URLSession.shared.dataTask(with: exampleURL) { (data, response, error) in
// data is your mocked data
}.resume()
除了已经构建的静态 DataType
实现之外,还可以创建自定义的实现,这些实现将用作 Content-Type
标头键的值。
let xmlURL = URL(string: "https://www.wetransfer.com/sample-xml.xml")!
Mock(fileExtensions: "png", contentType: .init(name: "xml", headerValue: "text/xml"), statusCode: 200, data: [
.get: try! Data(contentsOf: MockedData.sampleXML)
]).register()
URLSession.shared.dataTask(with: xmlURL) { (data, response, error) in
let sampleXML: Data = data // This is the xml from your resources.
}.resume(
有时您想测试请求的取消是否有效。 在这种情况下,模拟的请求不应立即完成,并且您需要延迟。 可以轻松添加此项
let exampleURL = URL(string: "https://www.wetransfer.com/api/endpoint")!
var mock = Mock(url: exampleURL, contentType: .json, statusCode: 200, data: [
.head: try! Data(contentsOf: MockedData.headResponse),
.get: try! Data(contentsOf: MockedData.exampleJSON)
])
mock.delay = DispatchTimeInterval.seconds(5)
mock.register()
有时您想模拟短 URL 或其他重定向 URL。 通过保存响应并模拟重定向位置(可以在响应中找到),可以实现此目的
Date: Tue, 10 Oct 2017 07:28:33 GMT
Location: https://wetransfer.com/redirect
通过为短 URL 和重定向 URL 创建 mock,您可以模拟重定向并测试此行为
let urlWhichRedirects: URL = URL(string: "https://we.tl/redirect")!
Mock(url: urlWhichRedirects, contentType: .html, statusCode: 200, data: [.get: try! Data(contentsOf: MockedData.redirectGET)]).register()
Mock(url: URL(string: "https://wetransfer.com/redirect")!, contentType: .json, statusCode: 200, data: [.get: try! Data(contentsOf: MockedData.exampleJSON)]).register()
由于 Mocker 在注册后默认捕获所有 URL,因此在不需要模拟请求的情况下,您可能会最终抛出 fatalError
。 在这种情况下,您可以忽略该 URL
let ignoredURL = URL(string: "https://www.wetransfer.com")!
// Ignore any requests that exactly match the URL
Mocker.ignore(ignoredURL)
// Ignore any requests that match the URL, with any query parameters
// e.g. https://www.wetransfer.com?foo=bar would be ignored
Mocker.ignore(ignoredURL, matchType: .ignoreQuery)
// Ignore any requests that begin with the URL
// e.g. https://www.wetransfer.com/api/v1 would be ignored
Mocker.ignore(ignoredURL, matchType: .prefix)
但是,如果您需要 Mocker 仅捕获模拟的 URL 并忽略所有其他 URL,则可以将 mode
属性设置为 .optin
。
Mocker.mode = .optin
如果要将原始模式恢复,只需将其设置为 .optout
即可。
Mocker.mode = .optout
您可以请求 Mock
返回错误,从而允许测试错误处理。
Mock(url: originalURL, contentType: .json, statusCode: 500, data: [.get: Data()],
requestError: TestExampleError.example).register()
URLSession.shared.dataTask(with: originalURL) { (data, urlresponse, err) in
XCTAssertNil(data)
XCTAssertNil(urlresponse)
XCTAssertNotNil(err)
if let err = err {
// there's not a particularly elegant way to verify an instance
// of an error, but this is a convenient workaround for testing
// purposes
XCTAssertEqual("example", String(describing: err))
}
expectation.fulfill()
}.resume()
您可以在 Mock
回调上注册以使测试更容易。
var mock = Mock(url: request.url!, contentType: .json, statusCode: 200, data: [.post: Data()])
mock.onRequestHandler = OnRequestHandler(httpBodyType: [[String:String]].self, callback: { request, postBodyArguments in
XCTAssertEqual(request.url, mock.request.url)
XCTAssertEqual(expectedParameters, postBodyArguments)
onRequestExpectation.fulfill()
})
mock.completion = {
endpointIsCalledExpectation.fulfill()
}
mock.register()
除了设置 completion
和 onRequest
之外,您还可以使用预期。
var mock = Mock(url: url, contentType: .json, statusCode: 200, data: [.get: Data()])
let requestExpectation = expectationForRequestingMock(&mock)
let completionExpectation = expectationForCompletingMock(&mock)
mock.register()
URLSession.shared.dataTask(with: URLRequest(url: url)).resume()
wait(for: [requestExpectation, completionExpectation], timeout: 2.0)
您可以清除所有已注册的 mocks
Mocker.removeAll()
Carthage 是一个去中心化的依赖管理工具,它可以构建您的依赖项并为您提供二进制框架。
您可以使用 Homebrew 使用以下命令安装 Carthage
$ brew update
$ brew install carthage
要使用 Carthage 将 Mocker 集成到您的 Xcode 项目中,请在您的 Cartfile
中指定它
github "WeTransfer/Mocker" ~> 3.0.0
运行 carthage update
以构建框架,并将构建的 Mocker.framework
拖到您的 Xcode 项目中。
Swift Package Manager 是一种用于管理 Swift 代码分发的工具。 它与 Swift 构建系统集成,以自动化下载、编译和链接依赖项的过程。
将 Mocker 作为包添加到您的 Package.swift
文件,然后将其指定为您希望使用它的 Target 的依赖项。
import PackageDescription
let package = Package(
name: "MyProject",
platforms: [
.macOS(.v10_15)
],
dependencies: [
.package(url: "https://github.com/WeTransfer/Mocker.git", .upToNextMajor(from: "3.0.0"))
],
targets: [
.target(
name: "MyProject",
dependencies: ["Mocker"]),
.testTarget(
name: "MyProjectTests",
dependencies: ["MyProject"]),
]
)
要将 Mocker 作为 依赖项 添加到您的 Xcode 项目,请选择 File > Swift Packages > Add Package Dependency 并输入存储库 URL。
如果您收到以下错误:cannot find auto-link library XCTest and XCTestSwiftSupport,请将 Build Options 下的以下属性从 No 设置为 Yes。
ENABLE_TESTING_SEARCH_PATHS to YES
如果您不想使用任何上述依赖项管理器,您可以手动将 Mocker 集成到您的项目中。
打开 Terminal,cd
进入您的顶级项目目录,并运行以下命令“如果”您的项目未初始化为 git 存储库
$ git init
通过运行以下命令将 Mocker 添加为 git 子模块
$ git submodule add https://github.com/WeTransfer/Mocker.git
打开新的 Mocker
文件夹,并将 Mocker.xcodeproj
拖到应用程序 Xcode 项目的 Project Navigator 中。
它应显示在应用程序的蓝色项目图标下。 它是在所有其他 Xcode 组之上还是之下并不重要。
在 Project Navigator 中选择 Mocker.xcodeproj
并验证部署目标与应用程序目标是否匹配。
接下来,在 Project Navigator 中选择您的应用程序项目(蓝色项目图标)以导航到目标配置窗口,然后在侧边栏中的“Targets”标题下选择应用程序目标。
在该窗口顶部的标签栏中,打开“General”面板。
单击“Embedded Binaries”部分下的 +
按钮。
选择 Mocker.framework
。
就是这样!
Mocker.framework
会自动添加为目标依赖项、链接框架和嵌入框架,并在复制文件构建阶段中,这是在模拟器和设备上构建所需的一切。
有关更改列表,请参阅 CHANGELOG.md。
Mocker 在 MIT 许可证下可用。 有关更多信息,请参见 LICENSE 文件。