spiegel-keyman/ios/engine/KMEI/KeymanEngineTests/TestUtils/Downloading/URLSessionMock.swift
2024-01-19 11:29:25 +07:00

113 lines
3.6 KiB
Swift

//
// URLSessionMock.swift
// KeymanEngineTests
//
// Created by Joshua Horton on 5/27/20.
// Copyright © 2020 SIL International. All rights reserved.
//
import Foundation
import XCTest
extension TestUtils.Downloading {
class MockResult {
let location: URL?
let error: Error?
let statusCode: Int
init(location: URL?, error: Error?, statusCode: Int = 200) {
self.location = location
self.error = error
self.statusCode = statusCode
}
}
/**
* Many thanks to https://www.swiftbysundell.com/articles/mocking-in-swift/ for the approach used here.
*/
class URLSessionMock: URLSession {
enum Task {
case data(MockResult)
case download(MockResult)
}
enum URLSessionMockError: Error {
case unexpectedTaskType
case mockedFileNotFound
case dataInitializationError
case mockQueueEmpty
}
private var mockedResultQueue: [(Task, XCTestExpectation?)]
override init() {
mockedResultQueue = []
}
func queueMockResult(_ result: Task, expectation: XCTestExpectation? = nil) {
mockedResultQueue.append((result, expectation))
}
var queueIsEmpty: Bool {
get {
return mockedResultQueue.isEmpty
}
}
override func downloadTask(with url: URL, completionHandler: @escaping (URL?, URLResponse?, Error?) -> Void) -> URLSessionDownloadTask {
if(mockedResultQueue.count <= 0) {
return URLSessionDownloadTaskMock {
completionHandler(nil, nil, URLSessionMockError.mockQueueEmpty)
}
} else {
if case (.download(let response), let expectation) = self.mockedResultQueue.removeFirst() {
let mockedURLResponse = HTTPURLResponse(url: response.location!, statusCode: 200, httpVersion: nil, headerFields: nil)
return URLSessionDownloadTaskMock(response: mockedURLResponse) {
completionHandler(response.location, mockedURLResponse, response.error)
expectation?.fulfill()
}
} else {
return URLSessionDownloadTaskMock {
completionHandler(nil, nil, URLSessionMockError.unexpectedTaskType)
}
}
}
}
override func dataTask(with url: URL, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask {
if(mockedResultQueue.count <= 0) {
return URLSessionDataTaskMock {
completionHandler(nil, nil, URLSessionMockError.mockQueueEmpty)
}
} else {
return URLSessionDataTaskMock {
if case (.data(let response), let expectation) = self.mockedResultQueue.removeFirst() {
if !FileManager.default.fileExists(atPath: response.location!.path) {
// TODO: Properly mock HTTPURLSession for 404 errors?
completionHandler(nil, nil, URLSessionMockError.mockedFileNotFound)
expectation?.fulfill()
return
}
do {
let data = try Data(contentsOf: response.location!, options: .mappedIfSafe)
if(data.isEmpty) {
completionHandler(nil, nil, URLSessionMockError.dataInitializationError)
}
// A bit of white-box testing - we know that HTTPDownloader doesn't actually examine
// the URLResponse headers, so we don't provide any.
completionHandler(data, nil, response.error)
} catch {
completionHandler(nil, nil, URLSessionMockError.dataInitializationError)
}
expectation?.fulfill()
} else {
completionHandler(nil, nil, URLSessionMockError.unexpectedTaskType)
}
}
}
}
}
}