Skip to content

Commit

Permalink
mixing in the ability to send an explicit error from a mock response (#…
Browse files Browse the repository at this point in the history
…52)

* mixing in the ability to send an explicit error from a mock response in order to test error handling

* adding a test to verify the code works and show how it might be used

* adding a note to README for how to request an error response from a mock
  • Loading branch information
heckj authored May 22, 2020
1 parent a7ddd48 commit 036f58f
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 3 deletions.
30 changes: 30 additions & 0 deletions MockerTests/MockerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -346,4 +346,34 @@ final class MockerTests: XCTestCase {
request.httpMethod = Mock.HTTPMethod.put.rawValue
XCTAssertNotNil(Mocker.mock(for: request))
}

/// it should return the error we requested from the mock when we pass in an Error.
func testMockReturningError() {
let expectation = self.expectation(description: "Data request should succeed")
let originalURL = URL(string: "https://www.wetransfer.com/example.json")!

enum TestExampleError: Error {
case example
}

Mock(url: originalURL, dataType: .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()

waitForExpectations(timeout: 10.0, handler: nil)

}
}
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,28 @@ mock.completion = {
mock.register()
```

##### Mock errors

You can request a `Mock` to return an error, allowing testing of error handling.

```swift
Mock(url: originalURL, dataType: .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()
```

## Communication

Expand Down
11 changes: 8 additions & 3 deletions Sources/Mock.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ public struct Mock: Equatable {
/// The type of the data which is returned.
public let dataType: DataType

/// If set, the error that URLProtocol will report as a result rather than returning data from the mock
public let requestError: Error?

/// The headers to send back with the response.
public let headers: [String: String]

Expand Down Expand Up @@ -101,14 +104,15 @@ public struct Mock: Equatable {
/// The callback which will be executed everytime this `Mock` was started. Can be used within unit tests for validating that a request has been started. The callback must be set before calling `register`.
public var onRequest: OnRequest?

private init(url: URL? = nil, ignoreQuery: Bool = false, dataType: DataType, statusCode: Int, data: [HTTPMethod: Data], additionalHeaders: [String: String] = [:], fileExtensions: [String]? = nil) {
private init(url: URL? = nil, ignoreQuery: Bool = false, dataType: DataType, statusCode: Int, data: [HTTPMethod: Data], requestError: Error? = nil, additionalHeaders: [String: String] = [:], fileExtensions: [String]? = nil) {
self.urlToMock = url
let generatedURL = URL(string: "https://mocked.wetransfer.com/\(dataType.rawValue)/\(statusCode)/\(data.keys.first!.rawValue)")!
self.generatedURL = generatedURL
var request = URLRequest(url: url ?? generatedURL)
request.httpMethod = data.keys.first!.rawValue
self.request = request
self.ignoreQuery = ignoreQuery
self.requestError = requestError
self.dataType = dataType
self.statusCode = statusCode
self.data = data
Expand Down Expand Up @@ -136,12 +140,13 @@ public struct Mock: Equatable {
/// - Parameters:
/// - url: The URL to match for and to return the mocked data for.
/// - ignoreQuery: If `true`, checking the URL will ignore the query and match only for the scheme, host and path. Defaults to `false`.
/// - reportFailure: if `true`, the URLsession will report an error loading the URL rather than returning data. Defaults to `false`.
/// - dataType: The type of the data which is returned.
/// - statusCode: The HTTP status code to return with the response.
/// - data: The data which will be returned as the response based on the HTTP Method.
/// - additionalHeaders: Additional headers to be added to the response.
public init(url: URL, ignoreQuery: Bool = false, dataType: DataType, statusCode: Int, data: [HTTPMethod: Data], additionalHeaders: [String: String] = [:]) {
self.init(url: url, ignoreQuery: ignoreQuery, dataType: dataType, statusCode: statusCode, data: data, additionalHeaders: additionalHeaders, fileExtensions: nil)
public init(url: URL, ignoreQuery: Bool = false, dataType: DataType, statusCode: Int, data: [HTTPMethod: Data], additionalHeaders: [String: String] = [:], requestError: Error? = nil) {
self.init(url: url, ignoreQuery: ignoreQuery, dataType: dataType, statusCode: statusCode, data: data, requestError: requestError, additionalHeaders: additionalHeaders, fileExtensions: nil)
}

/// Creates a `Mock` for the given file extensions. The mock will only be used for urls matching the extension.
Expand Down
5 changes: 5 additions & 0 deletions Sources/MockingURLProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public final class MockingURLProtocol: URLProtocol {

enum Error: Swift.Error, LocalizedError, CustomDebugStringConvertible {
case missingMockedData(url: String)
case explicitMockFailure(url: String)

var errorDescription: String? {
return debugDescription
Expand All @@ -22,6 +23,8 @@ public final class MockingURLProtocol: URLProtocol {
switch self {
case .missingMockedData(let url):
return "Missing mock for URL: \(url)"
case .explicitMockFailure(url: let url):
return "Induced error for URL: \(url)"
}
}
}
Expand Down Expand Up @@ -60,6 +63,8 @@ public final class MockingURLProtocol: URLProtocol {
private func finishRequest(for mock: Mock, data: Data, response: HTTPURLResponse) {
if let redirectLocation = data.redirectLocation {
self.client?.urlProtocol(self, wasRedirectedTo: URLRequest(url: redirectLocation), redirectResponse: response)
} else if let requestError = mock.requestError {
self.client?.urlProtocol(self, didFailWithError: requestError)
} else {
self.client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed)
self.client?.urlProtocol(self, didLoad: data)
Expand Down

0 comments on commit 036f58f

Please sign in to comment.