From 60a9bffc1b4a71c592c713178c8a55af69cb243a Mon Sep 17 00:00:00 2001 From: Matthias Lamoureux Date: Wed, 24 Feb 2021 10:54:57 +0100 Subject: [PATCH] feat: Global mode to choose only to mock registered routes (#84) * feat: Added a global mode selection to be able to only mock registered routes, and let every other url to be processed as if the Mocker is not present. * refactor: Reorganizing subtype position and fixing documentation following @avdlee advices. * fix: Added unit tests to Mocker mode * fix: Fixing SwiftLint rule `switch_case_on_newline` * fix: Updated documentation chapter on Ignoring URLs to add the new mode * fix: typo --- MockerTests/MockerTests.swift | 54 +++++++++++++++++++++++++++++++++++ README.md | 14 ++++++++- Sources/Mocker.swift | 42 +++++++++++++++++++++------ 3 files changed, 100 insertions(+), 10 deletions(-) diff --git a/MockerTests/MockerTests.swift b/MockerTests/MockerTests.swift index 7d146f0..739133f 100644 --- a/MockerTests/MockerTests.swift +++ b/MockerTests/MockerTests.swift @@ -441,4 +441,58 @@ final class MockerTests: XCTestCase { waitForExpectations(timeout: 10.0, handler: nil) } + + /// It should process unknown URL + func testMockerOptoutMode() { + Mocker.mode = .optout + + let mockedURL = URL(string: "www.google.com")! + let ignoredURL = URL(string: "www.wetransfer.com")! + let unknownURL = URL(string: "www.netflix.com")! + + // Mocking + Mock(url: mockedURL, dataType: .json, statusCode: 200, data: [.get: Data()]) + .register() + + // Ignoring + Mocker.ignore(ignoredURL) + + // Checking mocked URL are processed by Mocker + XCTAssertTrue(MockingURLProtocol.canInit(with: URLRequest(url: mockedURL))) + // Checking ignored URL are not processed by Mocker + XCTAssertFalse(MockingURLProtocol.canInit(with: URLRequest(url: ignoredURL))) + + // Checking unknown URL are processed by Mocker (.optout mode) + XCTAssertTrue(MockingURLProtocol.canInit(with: URLRequest(url: unknownURL))) + } + + /// It should not process unknown URL + func testMockerOptinMode() { + Mocker.mode = .optin + + let mockedURL = URL(string: "www.google.com")! + let ignoredURL = URL(string: "www.wetransfer.com")! + let unknownURL = URL(string: "www.netflix.com")! + + // Mocking + Mock(url: mockedURL, dataType: .json, statusCode: 200, data: [.get: Data()]) + .register() + + // Ignoring + Mocker.ignore(ignoredURL) + + // Checking mocked URL are processed by Mocker + XCTAssertTrue(MockingURLProtocol.canInit(with: URLRequest(url: mockedURL))) + // Checking ignored URL are not processed by Mocker + XCTAssertFalse(MockingURLProtocol.canInit(with: URLRequest(url: ignoredURL))) + + // Checking unknown URL are not processed by Mocker (.optin mode) + XCTAssertFalse(MockingURLProtocol.canInit(with: URLRequest(url: unknownURL))) + } + + /// Default mode should be .optout + func testDefaultMode() { + /// Checking default mode + XCTAssertEqual(.optout, Mocker.mode) + } } diff --git a/README.md b/README.md index a162127..8964782 100644 --- a/README.md +++ b/README.md @@ -181,13 +181,25 @@ Mock(url: URL(string: "https://wetransfer.com/redirect")!, dataType: .json, stat ``` ##### Ignoring URLs -As the Mocker catches all URLs when registered, you might end up with a `fatalError` thrown in cases you don't need a mocked request. In that case you can ignore the URL: +As the Mocker catches all URLs by default when registered, you might end up with a `fatalError` thrown in cases you don't need a mocked request. In that case you can ignore the URL: ```swift let ignoredURL = URL(string: "www.wetransfer.com")! Mocker.ignore(ignoredURL) ``` +However if you need the Mocker to catch only mocked URLs and ignore every other URL, you can set the `mode` attribute to `.optin`. + +```swift +Mocker.mode = .optin +``` + +If you want to set the original mode back, you have just to set it to `.optout`. + +```swift +Mocker.mode = .optout +``` + ##### Mock errors You can request a `Mock` to return an error, allowing testing of error handling. diff --git a/Sources/Mocker.swift b/Sources/Mocker.swift index 55bf56d..96b2976 100644 --- a/Sources/Mocker.swift +++ b/Sources/Mocker.swift @@ -32,16 +32,35 @@ public struct Mocker { case http1_1 = "HTTP/1.1" case http2_0 = "HTTP/2.0" } - + + /// The way Mocker handles unregistered urls + public enum Mode { + /// The default mode: only URLs registered with the `ignore(_ url: URL)` method are ignored for mocking. + /// + /// - Registered mocked URL: Mocked. + /// - Registered ignored URL: Ignored by Mocker, default process is applied as if the Mocker doesn't exist. + /// - Any other URL: Raises an error. + case optout + + /// Only registered mocked URLs are mocked, all others pass through. + /// + /// - Registered mocked URL: Mocked. + /// - Any other URL: Ignored by Mocker, default process is applied as if the Mocker doesn't exist. + case optin + } + + /// The mode defines how unknown URLs are handled. Defaults to `optout` which means requests without a mock will fail. + public static var mode: Mode = .optout + /// The shared instance of the Mocker, can be used to register and return mocks. internal static var shared = Mocker() - + /// The HTTP Version to use in the mocked response. public static var httpVersion: HTTPVersion = HTTPVersion.http1_1 - + /// The registrated mocks. private(set) var mocks: [Mock] = [] - + /// URLs to ignore for mocking. public var ignoredURLs: [URL] { ignoredRules.map { $0.urlToIgnore } @@ -56,7 +75,7 @@ public struct Mocker { // Whenever someone is requesting the Mocker, we want the URL protocol to be activated. URLProtocol.registerClass(MockingURLProtocol.self) } - + /// Register new Mocked data. If a mock for the same URL and HTTPMethod exists, it will be overwritten. /// /// - Parameter mock: The Mock to be registered for future requests. @@ -67,7 +86,7 @@ public struct Mocker { shared.mocks.append(mock) } } - + /// Register an URL to ignore for mocking. This will let the URL work as if the Mocker doesn't exist. /// /// - Parameter url: The URL to mock. @@ -78,14 +97,19 @@ public struct Mocker { shared.ignoredRules.append(rule) } } - + /// Checks if the passed URL should be handled by the Mocker. If the URL is registered to be ignored, it will not handle the URL. /// /// - Parameter url: The URL to check for. /// - Returns: `true` if it should be mocked, `false` if the URL is registered as ignored. public static func shouldHandle(_ url: URL) -> Bool { shared.queue.sync { - return !shared.ignoredRules.contains(where: { $0.shouldIgnore(url) }) + switch mode { + case .optout: + return !shared.ignoredRules.contains(where: { $0.shouldIgnore(url) }) + case .optin: + return shared.mocks.contains(where: { $0.url == url }) + } } } @@ -95,7 +119,7 @@ public struct Mocker { shared.mocks.removeAll() } } - + /// Retrieve a Mock for the given request. Matches on `request.url` and `request.httpMethod`. /// /// - Parameter request: The request to search for a mock.