diff --git a/MockerTests/MockerTests.swift b/MockerTests/MockerTests.swift index 819682f..0ce11ed 100644 --- a/MockerTests/MockerTests.swift +++ b/MockerTests/MockerTests.swift @@ -268,7 +268,7 @@ final class MockerTests: XCTestCase { let onRequestExpectation = expectation(description: "Data request should start") let completionExpectation = expectation(description: "Data request should succeed") var mock = Mock(dataType: .json, statusCode: 200, data: [.get: Data()]) - mock.onRequest = { + mock.onRequest = { _, _ in onRequestExpectation.fulfill() } mock.completion = { @@ -281,6 +281,28 @@ final class MockerTests: XCTestCase { wait(for: [onRequestExpectation, completionExpectation], timeout: 2.0, enforceOrder: true) } + /// It should report post body arguments if they exist. + func testOnRequestPostBodyParameters() throws { + let onRequestExpectation = expectation(description: "Data request should start") + + let expectedParameters = ["test": "value"] + var request = URLRequest(url: URL(string: "https://www.fakeurl.com")!) + request.httpMethod = Mock.HTTPMethod.post.rawValue + request.httpBody = try JSONSerialization.data(withJSONObject: expectedParameters, options: .prettyPrinted) + + var mock = Mock(url: request.url!, dataType: .json, statusCode: 200, data: [.post: Data()]) + mock.onRequest = { request, postBodyArguments in + XCTAssertEqual(request.url, mock.request.url) + XCTAssertEqual(expectedParameters, postBodyArguments as? [String: String]) + onRequestExpectation.fulfill() + } + mock.register() + + URLSession.shared.dataTask(with: request).resume() + + wait(for: [onRequestExpectation], timeout: 2.0) + } + /// It should call the mock after a delay. func testDelayedMock() { let nonDelayExpectation = expectation(description: "Data request should succeed") diff --git a/README.md b/README.md index 8d447d7..02feb7d 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ Mocker is a library written in Swift which makes it possible to mock data reques - [Delayed responses](#delayed-responses) - [Redirect responses](#redirect-responses) - [Ignoring URLs](#ignoring-urls) + - [Mock callbacks](#mock-callbacks) - [Communication](#communication) - [Installation](#installation) - [Release Notes](#release-notes) @@ -186,6 +187,23 @@ let ignoredURL = URL(string: "www.wetransfer.com")! Mocker.ignore(ignoredURL) ``` +##### Mock callbacks +You can register on `Mock` callbacks to make testing easier. + +```swift +var mock = Mock(url: request.url!, dataType: .json, statusCode: 200, data: [.post: Data()]) +mock.onRequest = { request, postBodyArguments in + XCTAssertEqual(request.url, mock.request.url) + XCTAssertEqual(expectedParameters, postBodyArguments as? [String: String]) + onRequestExpectation.fulfill() +} +mock.completion = { + endpointIsCalledExpectation.fulfill() +} +mock.register() +``` + + ## Communication - If you **found a bug**, open an issue. diff --git a/Sources/Mock.swift b/Sources/Mock.swift index 023330b..8b0be4b 100644 --- a/Sources/Mock.swift +++ b/Sources/Mock.swift @@ -12,7 +12,7 @@ import Foundation /// A Mock which can be used for mocking data requests with the `Mocker` by calling `Mocker.register(...)`. public struct Mock: Equatable { - + /// HTTP method definitions. /// /// See https://tools.ietf.org/html/rfc7231#section-4.3 @@ -54,6 +54,8 @@ public struct Mock: Equatable { } } } + + public typealias OnRequest = (_ request: URLRequest, _ httpBodyArguments: [String: Any]?) -> Void /// The type of the data which is returned. public let dataType: DataType @@ -97,7 +99,7 @@ public struct Mock: Equatable { public var completion: (() -> Void)? /// 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. - public var onRequest: (() -> Void)? + 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) { self.urlToMock = url diff --git a/Sources/MockingURLProtocol.swift b/Sources/MockingURLProtocol.swift index 21d29a5..51997ba 100644 --- a/Sources/MockingURLProtocol.swift +++ b/Sources/MockingURLProtocol.swift @@ -40,7 +40,9 @@ public final class MockingURLProtocol: URLProtocol { return } - mock.onRequest?() + if let onRequest = mock.onRequest { + onRequest(request, request.postBodyArguments) + } guard let delay = mock.delay else { finishRequest(for: mock, data: data, response: response) @@ -97,3 +99,32 @@ private extension Data { return redirectLocation } } + +private extension URLRequest { + var postBodyArguments: [String: Any]? { + guard let httpBody = httpBodyStreamData() else { return nil } + return try? JSONSerialization.jsonObject(with: httpBody, options: .fragmentsAllowed) as? [String: Any] + } + + /// We need to use the http body stream data as the URLRequest once launched converts the `httpBody` to this stream of data. + private func httpBodyStreamData() -> Data? { + guard let bodyStream = self.httpBodyStream else { return nil } + + bodyStream.open() + + // Will read 16 chars per iteration. Can use bigger buffer if needed + let bufferSize: Int = 16 + let buffer = UnsafeMutablePointer.allocate(capacity: bufferSize) + var data = Data() + + while bodyStream.hasBytesAvailable { + let readData = bodyStream.read(buffer, maxLength: bufferSize) + data.append(buffer, count: readData) + } + + buffer.deallocate() + bodyStream.close() + + return data + } +}