Skip to content

Commit

Permalink
Ability to mock ExecuteRequest & ExecuteBatchRequest
Browse files Browse the repository at this point in the history
  • Loading branch information
ilia3546 committed Oct 20, 2023
1 parent 150f6b5 commit 3e52df8
Show file tree
Hide file tree
Showing 5 changed files with 203 additions and 73 deletions.
13 changes: 9 additions & 4 deletions Sources/Models/OperationContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -67,14 +67,19 @@ internal struct OperationContextData: OperationContext {
}

init<O: GraphQLOperation>(operation: O) {
self.mode = O.RootSchema.mode
self.operationName = O.operationName
self.query = O.buildQuery()
self.variables = O.Variables.allKeys.reduce(into: [String: Variable](), {
let variables = O.Variables.allKeys.reduce(into: [String: Variable](), {
guard let value = operation.variables[keyPath: $1] as? Variable else { return }
if !operation.variables.encodeNull, value.json == nil { return }
$0[$1.identifier] = value
})
self.init(operation: O.self, variables: variables)
}

init<O: GraphQLOperation>(operation: O.Type, variables: [String: Variable]) {
self.mode = O.RootSchema.mode
self.operationName = O.operationName
self.query = O.buildQuery()
self.variables = variables
}

public func variables(prettyPrinted: Bool = false) -> String? {
Expand Down
2 changes: 1 addition & 1 deletion Sources/Network/Client+Execute.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public extension Client {
self.append(uploads: operationContext.getUploads(), to: multipartFormData)

let dataRequest = self.prepareDataRequest(for: O.self, with: multipartFormData, url: self.url)
return ExecuteRequest(
return ExecuteRequestImpl(
client: self,
alamofireRequest: dataRequest,
decodePath: O.decodePath(of: O.ResponseValue.self),
Expand Down
2 changes: 1 addition & 1 deletion Sources/Network/Client+ExecuteBatch.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public extension Client {
}

let dataRequest = self.prepareDataRequest(for: O.self, with: multipartFormData, url: batchUrl)
return ExecuteBatchRequest(
return ExecuteBatchRequestImpl(
client: self,
alamofireRequest: dataRequest,
decodePath: O.decodePath(of: O.ResponseValue.self),
Expand Down
132 changes: 97 additions & 35 deletions Sources/Network/ExecuteBatchRequest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,40 +13,91 @@ public class ExecuteBatchRequest<O: GraphQLOperation>: SuccessableRequest {

public typealias ResultValue = [O.Value]

public let context: OperationContext
public var request: URLRequest? { nil }
public var task: URLSessionTask? { nil }

fileprivate let queue: DispatchQueue
fileprivate var closureStorage = ExecuteClosureStorage<ResultValue>()
public fileprivate(set) var isSending = false

init(context: OperationContext, queue: DispatchQueue) {
self.context = context
self.queue = queue
}

public func cancel() {
fatalError("Override cancel func in \(type(of: Self.self))")
}

fileprivate func send() {
fatalError("Override send func in \(type(of: Self.self))")
}

@discardableResult
public func onSuccess(_ closure: @escaping SuccessClosure) -> ProgressableRequest {
self.closureStorage.successClosure = closure
self.send()
return self
}

@discardableResult
public func onProgress(_ closure: @escaping ProgressClosure) -> FailureableRequest {
self.closureStorage.progressClosure = closure
self.send()
return self
}

@discardableResult
public func onFailure(_ closure: @escaping FailureClosure) -> FinishableRequest {
self.closureStorage.failureClosure = closure
self.send()
return self
}

@discardableResult
public func onFinish(_ closure: @escaping FinishClosure) -> GrapheneRequest {
self.closureStorage.finishClosure = closure
self.send()
return self
}

}

public class ExecuteBatchRequestImpl<O: GraphQLOperation>: ExecuteBatchRequest<O> {

private let alamofireRequest: DataRequest
private weak var client: Client?
private let jsonDecoder: JSONDecoder
private let muteCanceledRequests: Bool
private let monitor: CompositeGrapheneEventMonitor
private let errorModifier: Client.Configuration.ErrorModifier?
private let queue: DispatchQueue
private var isSent = false
private var closureStorage = ExecuteClosureStorage<ResultValue>()
public let context: OperationContext
public var request: URLRequest? { self.alamofireRequest.request }
public var task: URLSessionTask? { self.alamofireRequest.task }

override public var request: URLRequest? { self.alamofireRequest.request }
override public var task: URLSessionTask? { self.alamofireRequest.task }

internal init(client: Client, alamofireRequest: DataRequest, decodePath: String?, context: OperationContext, queue: DispatchQueue) {
self.client = client
self.monitor = CompositeGrapheneEventMonitor(monitors: client.configuration.eventMonitors)
self.muteCanceledRequests = client.configuration.muteCanceledRequests
self.errorModifier = client.configuration.errorModifier
self.alamofireRequest = alamofireRequest
self.context = context
self.queue = queue
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = client.configuration.keyDecodingStrategy
decoder.dateDecodingStrategy = client.configuration.dateDecodingStrategy
decoder.userInfo[.operationName] = context.operationName
self.jsonDecoder = decoder

super.init(context: context, queue: queue)

self.alamofireRequest
.uploadProgress(queue: self.queue, closure: self.handleProgress(_:))
.responseData(queue: .global(qos: .utility), completionHandler: self.handleResponse(_:))
}

private func send() {
guard !self.isSent else { return }
self.isSent = true
override fileprivate func send() {
guard !self.isSending else { return }
self.isSending = true
self.alamofireRequest.resume()
if let client = self.client {
self.monitor.client(client, didSend: self)
Expand All @@ -62,6 +113,10 @@ public class ExecuteBatchRequest<O: GraphQLOperation>: SuccessableRequest {

private func handleResponse(_ dataResponse: DataResponse<Data, AFError>) {

defer {
self.isSending = false
}

if self.muteCanceledRequests, dataResponse.error?.isExplicitlyCancelledError ?? false {
return
}
Expand Down Expand Up @@ -122,37 +177,44 @@ public class ExecuteBatchRequest<O: GraphQLOperation>: SuccessableRequest {

}

@discardableResult
public func onSuccess(_ closure: @escaping SuccessClosure) -> ProgressableRequest {
self.closureStorage.successClosure = closure
self.send()
return self
override public func cancel() {
self.alamofireRequest.cancel()
}

@discardableResult
public func onProgress(_ closure: @escaping ProgressClosure) -> FailureableRequest {
self.closureStorage.progressClosure = closure
self.send()
return self
}
}

@discardableResult
public func onFailure(_ closure: @escaping FailureClosure) -> FinishableRequest {
self.closureStorage.failureClosure = closure
self.send()
return self
public class ExecuteBatchRequestMock<O: GraphQLOperation>: ExecuteBatchRequest<O> {

public let mockedResult: Result<ResultValue, Error>
public let timeout: TimeInterval
private var responseWorkItem: DispatchWorkItem?

public init(result: Result<ResultValue, Error>, timeout: TimeInterval, queue: DispatchQueue = .main) {
self.mockedResult = result
self.timeout = timeout
super.init(context: BatchOperationContextData(operation: O.self, operationContexts: []), queue: queue)
}

@discardableResult
public func onFinish(_ closure: @escaping FinishClosure) -> GrapheneRequest {
self.closureStorage.finishClosure = closure
self.send()
return self
override func send() {
guard !self.isSending else { return }
self.isSending = true
let responseWorkItem = DispatchWorkItem(block: { [weak self] in
guard let self else { return }
do {
let value = try self.mockedResult.get()
try self.closureStorage.successClosure?(value)
} catch {
self.closureStorage.failureClosure?(error)
}
self.closureStorage.finishClosure?()
})
self.queue.asyncAfter(deadline: .now() + self.timeout, execute: responseWorkItem)
self.responseWorkItem = responseWorkItem
}

public func cancel() {
self.alamofireRequest.cancel()
self.isSent = false
override public func cancel() {
self.responseWorkItem?.cancel()
self.isSending = false
}

}
127 changes: 95 additions & 32 deletions Sources/Network/ExecuteRequest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,26 +13,81 @@ public class ExecuteRequest<O: GraphQLOperation>: SuccessableRequest {

public typealias ResultValue = O.Value

public let context: OperationContext
public fileprivate(set) var isSending = false

public var request: URLRequest? {
nil
}

public var task: URLSessionTask? {
nil
}

fileprivate let queue: DispatchQueue
fileprivate var closureStorage = ExecuteClosureStorage<ResultValue>()

init(context: OperationContext, queue: DispatchQueue) {
self.context = context
self.queue = queue
}

public func cancel() {
fatalError("Override cancel func in \(type(of: Self.self))")
}

fileprivate func send() {
fatalError("Override send func in \(type(of: Self.self))")
}

@discardableResult
public func onSuccess(_ closure: @escaping SuccessClosure) -> ProgressableRequest {
self.closureStorage.successClosure = closure
self.send()
return self
}

@discardableResult
public func onProgress(_ closure: @escaping ProgressClosure) -> FailureableRequest {
self.closureStorage.progressClosure = closure
self.send()
return self
}

@discardableResult
public func onFailure(_ closure: @escaping FailureClosure) -> FinishableRequest {
self.closureStorage.failureClosure = closure
self.send()
return self
}

@discardableResult
public func onFinish(_ closure: @escaping FinishClosure) -> GrapheneRequest {
self.closureStorage.finishClosure = closure
self.send()
return self
}

}

internal class ExecuteRequestImpl<O: GraphQLOperation>: ExecuteRequest<O> {

private let alamofireRequest: DataRequest
private weak var client: Client?
private let muteCanceledRequests: Bool
private let monitor: CompositeGrapheneEventMonitor
private let queue: DispatchQueue
private let errorModifier: Client.Configuration.ErrorModifier?
private var closureStorage = ExecuteClosureStorage<ResultValue>()
public let context: OperationContext
public private(set) var isSending = false
public var request: URLRequest? { self.alamofireRequest.request }
public var task: URLSessionTask? { self.alamofireRequest.task }

override var request: URLRequest? { self.alamofireRequest.request }
override var task: URLSessionTask? { self.alamofireRequest.task }

internal init(client: Client, alamofireRequest: DataRequest, decodePath: String?, context: OperationContext, queue: DispatchQueue) {
self.client = client
self.monitor = CompositeGrapheneEventMonitor(monitors: client.configuration.eventMonitors)
self.muteCanceledRequests = client.configuration.muteCanceledRequests
self.errorModifier = client.configuration.errorModifier
self.alamofireRequest = alamofireRequest
self.context = context
self.queue = queue
super.init(context: context, queue: queue)
let jsonDecoder = JSONDecoder()
jsonDecoder.keyDecodingStrategy = client.configuration.keyDecodingStrategy
jsonDecoder.dateDecodingStrategy = client.configuration.dateDecodingStrategy
Expand All @@ -43,7 +98,7 @@ public class ExecuteRequest<O: GraphQLOperation>: SuccessableRequest {
.responseDecodable(queue: .global(qos: .utility), decoder: decoder, completionHandler: self.handleResponse(_:))
}

private func send() {
override fileprivate func send() {
guard !self.isSending else { return }
self.isSending = true
self.alamofireRequest.resume()
Expand Down Expand Up @@ -108,36 +163,44 @@ public class ExecuteRequest<O: GraphQLOperation>: SuccessableRequest {

}

@discardableResult
public func onSuccess(_ closure: @escaping SuccessClosure) -> ProgressableRequest {
self.closureStorage.successClosure = closure
self.send()
return self
override func cancel() {
self.alamofireRequest.cancel()
}

@discardableResult
public func onProgress(_ closure: @escaping ProgressClosure) -> FailureableRequest {
self.closureStorage.progressClosure = closure
self.send()
return self
}
}

@discardableResult
public func onFailure(_ closure: @escaping FailureClosure) -> FinishableRequest {
self.closureStorage.failureClosure = closure
self.send()
return self
public class ExecuteRequestMock<O: GraphQLOperation>: ExecuteRequest<O> {

public let mockedResult: Result<ResultValue, Error>
public let timeout: TimeInterval
private var responseWorkItem: DispatchWorkItem?

public init(result: Result<ResultValue, Error>, timeout: TimeInterval, queue: DispatchQueue = .main) {
self.mockedResult = result
self.timeout = timeout
super.init(context: OperationContextData(operation: O.self, variables: [:]), queue: queue)
}

@discardableResult
public func onFinish(_ closure: @escaping FinishClosure) -> GrapheneRequest {
self.closureStorage.finishClosure = closure
self.send()
return self
override func send() {
guard !self.isSending else { return }
self.isSending = true
let responseWorkItem = DispatchWorkItem(block: { [weak self] in
guard let self else { return }
do {
let value = try self.mockedResult.get()
try self.closureStorage.successClosure?(value)
} catch {
self.closureStorage.failureClosure?(error)
}
self.closureStorage.finishClosure?()
})
self.queue.asyncAfter(deadline: .now() + self.timeout, execute: responseWorkItem)
self.responseWorkItem = responseWorkItem
}

public func cancel() {
self.alamofireRequest.cancel()
override public func cancel() {
self.responseWorkItem?.cancel()
self.isSending = false
}

}
Expand Down

0 comments on commit 3e52df8

Please sign in to comment.