Mocker 是一个用 Swift 编写的库,它可以使用自定义的 URLProtocol 来模拟数据请求。

特性

离线运行所有数据请求单元测试 🎉

用法

Mocker 编写了单元测试,可以帮助您了解它的工作原理。

激活 Mocker

在您注册您的第一个 Mock 之后,mocker 将自动为默认的 URL 加载系统(例如 URLSession.shared)激活。

自定义 URLSessions

要使其与您的自定义 URLSession 一起使用,需要注册 MockingURLProtocol

let configuration = URLSessionConfiguration.default
configuration.protocolClasses = [MockingURLProtocol.self]
let urlSession = URLSession(configuration: configuration)
Alamofire

与在自定义 URLSession 上注册非常相似。

let configuration = URLSessionConfiguration.af.default
configuration.protocolClasses = [MockingURLProtocol.self]
let sessionManager = Alamofire.Session(configuration: configuration)

注册 Mock

创建你的模拟数据

建议创建一个包含所有可访问的模拟数据的类。 可以在此项目的单元测试中找到一个示例

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")!
}
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()
自定义 HEAD 和 GET 响应
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

除了已经构建的静态 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()
忽略 URLs

由于 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 返回错误,从而允许测试错误处理。

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 回调

您可以在 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()
Mock 预期

除了设置 completiononRequest 之外,您还可以使用预期。

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

清除所有已注册的 mocks

您可以清除所有已注册的 mocks

Mocker.removeAll()

交流

安装

Carthage

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 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"]),
    ]
)

Xcode

要将 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 集成到您的项目中。

嵌入式框架


发布说明

有关更改列表,请参阅 CHANGELOG.md

许可证

Mocker 在 MIT 许可证下可用。 有关更多信息,请参见 LICENSE 文件。