Sources/DDMock/DDMock.swift (133 lines of code) (raw):

import Foundation public class DDMock { private let mockDirectory = "/mockfiles" private let jsonExtension = "json" public static let versionString = "0.1.6" private var mockEntries = [String: MockEntry]() private(set) var strict: Bool = false // Enforces mocks only and no API fall-through private(set) var addMockHeader: Bool = true // Adds a mock header to show that mock is in use public private(set) var matchedPaths = [String]() // chronological order of paths public var onMissingMock: (_ path: String?) -> Void = {path in fatalError("missing stub for path: \(path ?? "<unknown>")") } public static let shared = DDMock() public func initialise(strict: Bool = false, addMockHeader: Bool = true) { self.strict = strict self.addMockHeader = addMockHeader let docsPath = Bundle.main.resourcePath! + mockDirectory let fileManager = FileManager.default fileManager.enumerator(atPath: docsPath)?.forEach({ (e) in if let e = e as? String, let url = URL(string: e) { if (url.pathExtension == jsonExtension) { createMockEntry(url: url) } } }) } public func clearHistory() { matchedPaths.removeAll() } private func createMockEntry(url: URL) { let fileName = "/" + url.lastPathComponent let key = url.path.replacingOccurrences(of: fileName, with: "") if var entry = mockEntries[key] { entry.files.append(url.path) mockEntries[key] = entry } else { mockEntries[key] = MockEntry(path: key, files: [url.path]) } } private func mockEntry(for path: String, isTest: Bool) -> MockEntry? { let entry = mockEntries[path] ?? getRegexEntry(path: path) guard !isTest else { return entry } // If strict mode is enabled, a missing entry is an error. Call handler. if strict && entry == nil { onMissingMock(path) } // Here we log the entries so that clients (like a unit test) can verify a call was made. matchedPaths.append(path) return entry } func hasMockEntry(path: String, method: String) -> EntrySetting { switch getMockEntry(path: path, method: method, isTest: true)?.useRealAPI() { case .none: return .notFound case .some(false): return .mocked case .some(true): return .useRealAPI } } func getMockEntry(path: String, method: String) -> MockEntry? { return getMockEntry(path: path, method: method, isTest: false) } private func getMockEntry(path: String, method: String, isTest: Bool) -> MockEntry? { guard let path = mockPath(path: path, method: method) else { return nil} return mockEntry(for: path, isTest: isTest) } func hasMockEntry(request: URLRequest) -> EntrySetting { guard let path = request.url?.path, let method = request.httpMethod else { return .notFound } return hasMockEntry(path: path, method: method) } func getMockEntry(request: URLRequest) -> MockEntry? { guard let path = request.url?.path, let method = request.httpMethod else { return nil } return getMockEntry(path: path, method: method, isTest: false) } private func getRegexEntry(path: String) -> MockEntry? { var matches = [MockEntry]() for key in mockEntries.keys { if (key.contains("_")) { let regex = key.replacingRegexMatches(pattern: "_[^/]*_", replaceWith: "[^/]*") if (path.matches(regex)) { if let match = mockEntries[key] { matches.append(match) } } } } guard matches.count <= 1 else { fatalError("Fatal Error: Multiple matches for regex entry.") } return matches.first } func getData(_ entry: MockEntry) -> Data? { var data: Data? = nil let f = entry.files[entry.getSelectedFile()] do { let docsPath = Bundle.main.resourcePath! + mockDirectory data = try Data(contentsOf: URL(fileURLWithPath: "\(docsPath)/\(f)"), options: .mappedIfSafe) } catch { data = nil } return data } } extension String { func matches(_ regex: String) -> Bool { return self.range(of: regex, options: .regularExpression, range: nil, locale: nil) != nil } func replacingRegexMatches(pattern: String, replaceWith: String = "") -> String { var newString = "" do { let regex = try NSRegularExpression(pattern: pattern, options: NSRegularExpression.Options.caseInsensitive) let range = NSMakeRange(0, self.count) newString = regex.stringByReplacingMatches(in: self, options: [], range: range, withTemplate: replaceWith) } catch { debugPrint("Error \(error)") } return newString } } extension DDMock { func mockPath(request: URLRequest) -> String? { if let url = request.url, let method = request.httpMethod { return mockPath(path: url.path, method: method) } else { return nil } } func mockPath(path: String, method: String) -> String? { return path.replacingRegexMatches(pattern: "^/", replaceWith: "") + "/" + method.lowercased() } }