public protocol ResponseJSONObjectSerializable {
init?(json: SwiftyJSON.JSON)
}
public struct Response<Value, Error: ErrorType> {
...
}
public func responseArray<T: ResponseJSONObjectSerializable>(completionHandler: Response<[T], NSError> -> Void) -> Self {
...
}
So far I understand the last function to mean that the type declaration requires generic type T which follows the protocol ResponseJSONObjectSerializable which is used in a completionHandler which takes a Response struct which has a type declaration of <Value, NSError> -> Void and then returns a self?
I feel like I might grok all of it except the last self part.
You're right about the first two declarations.
The last one is a bit odd because of how Alamofire does response serializers. You can chain multiple serializers like this:
Alamofire.request(myRequest)
.responseString { // handle response as string }
.responseArray { // handle response as array }
When that code gets called this is what happens:
Alamofire.request(myRequest) creates the request and a queue for handlers
.responseString & .responseArray get their response serializers added to the queue
The network call happens
When it's done (whether it fails or succeeds), the queue calls all of the response serializers that were added to it (i.e., .responseString & .responseArray)
When each serializer gets run, it's completion handler can be used to "return" results to the caller (which it can't do directly because it's async)
This code would does (almost) the same thing:
let manager = Alamofire.Manager.sharedInstance
manager.startsRequestImmediately = false
let alamofireRequest = manager.request(myRequest)
alamofireRequest.responseString { // handle response as string }
alamofireRequest.responseArray { // handle response as array }
alamofireRequest.resume()
But manager.startsRequestImmediately = false means that the network call doesn't get started until alamofireRequest.resume() is called. It's true by default so all of the response serializers have to be added as part of the same statement as manager.request(myRequest).
Since the response serializers return self, we can shorten this:
let alamofireRequest = manager.request(myRequest)
alamofireRequest.responseString { // handle response as string }
alamofireRequest.responseArray { // handle response as array }
To this:
let alamofireRequest = manager.request(myRequest)
.responseString { // handle response as string }
.responseArray { // handle response as array }
And if we use manager.startsRequestImmediately = true then we don't need the local var for the request at all (since we don't have to call alamofireRequest.resume()):
manager.request(myRequest)
.responseString { // handle response as string }
.responseArray { // handle response as array }
Related
I have the following method and I'm trying to unit test this in iOS/Swift
func apiResponseResults(response: AWSAPIGatewayResponse, sessionObject: Session) {
if response.statusCode == 200 {
// code
} else {
// code
}
}
This method is called from my api invoke method when the response is received. I want to unit test this by creating a fake AWSAPIGatewayResponse object.
I am not able to create AWSAPIGatewayResponse object. Is there a way to initialize this object in my unit testing class. I just wanna create an AWSAPIGatewayResponse object with statusCode as 200 and 300. Any help is appreciated.
Hi Pradeep, welcome to StackOverflow. 👋
You could define a protocol with the subset of functionality you are interested in from AWSAPIGatewayResponse, make AWSAPIGatewayResponse conform to it, and make apiResponseResults(response:, session:) expect a value of that type.
protocol Response {
var statusCode: Int { get }
}
extension AWSAPIGatewayResponse: Response { }
func apiResponseResults(response: Response, sessionObject: Session) { ... }
In your tests your can define a test double conforming to Response, and use that as the input parameter for your method.
struct TestResponse: Response {
let statusCode: Int
}
// Use it like this
let response = TestResponse(statusCode: 200)
Hope this helps.
I know this looks like a common question but after reading 10-15 tutorial and looking how can I write test for my service class. I can't solve moving static functions to protocol or etc.. for dependency injection
I have a network layer like below image. All my function classes (like fetch users, news, media etc..) calls "Service Caller" class and after that If response is error; calls "Service Error" class to handle error and If not error, decode the JSON.
My problem is that I'm calling service class as a static function like "ServiceCaller.performRequest" and If It gets error I'm also calling error class as static like "ServiceError.handle". Also It calls URLCache class to get path of request url. I'm not sure how can I make them dependency inject and mock in test class. As I find in tutorials, I should write it like;
protocol MyProtocol{
func myfunction() -> Void
}
class A{
let testProtocol = MyProtocol!
init(pro: MyProtocol){
testProtocol = pro
}
}
and in setup function in test class it probably;
myMockProtocol = ...
myTestclass = A.init(pro: myMockProtocol)
but I can't find how can I get ride of static calls like ServiceCaller.performRequest or ServiceError.handle..; (Simplified version in the bottom part of question)
class AppInitService{
static func initAppRequest(_ completion: #escaping (_ appInitRecevingModel: Result<AppInitRecevingModel>) -> Void) {
let sendingModel = AppInitSendingModel(cmsVersion: AppDefaults.instance.getCMSVersion())
let route = ServiceRouter(method: .post, path: URLCache.instance.getServiceURL(key: URLKeys.initApp), parameters: (sendingModel.getJSONData()), timeoutSec: 1)
ServiceCaller.performRequest(route: route) { (result) in
if let error = result.error{
if let statusCode = result.response?.statusCode{
completion(.error(ServiceError.handle(error: error, statusCode: statusCode)))
}else{
completion(.error(ServiceError.handle(error: error, statusCode: error._code)))
}
}else{
if let data = result.data{
do{
var responseJson = JSON(data)
responseJson["idleTimeoutInMinutes"] = 10
let input = try AppInitRecevingModel(data: responseJson.rawData())
completion(.success(input))
}catch let error{
completion(.error(ServiceError.handle(error: error, statusCode: -1002)))
}
}
}}
}
}
My Test class:
class MyProjectAppInitTests: XCTestCase {
var appInitTest: AppInitService!
override func setUp() {
super.setUp()
// Put setup code here. This method is called before the invocation of each test method in the class.
appInitTest = AppInitService.init()
}
override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class.
appInitTest = nil
super.tearDown()
}
func testExample() {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct results.
let testParamater = ["string":"test"]
let route = ServiceRouter(method: .post, path: "/testPath", parameters: testParamater.getJSONData(), timeoutSec: 10)
appInitTest. //cant call anything in here
}
Tutorials I looked for Unit Test;
https://www.raywenderlich.com/150073/ios-unit-testing-and-ui-testing-tutorial
https://www.swiftbysundell.com/posts/time-traveling-in-swift-unit-tests
https://marcosantadev.com/test-doubles-swift
http://merowing.info/2017/04/using-protocol-compositon-for-dependency-injection/
EDIT: One solution maybe writing init class for whole network layer and service classes then get rid of static functions? But I'm not sure If It will be a good approach.
EDIT 2: Simplified Code;
class A{
static func b(completion:...){
let paramater = ObjectModel(somevariable: SomeClass.Singleton.getVariable()) //Data that I sent on network request
let router = ServiceRouter(somevariable: SomeClassAgain.Singleton.getsomething()) //Router class which gets parameters, http method etc..
NetworkClass.performNetworkRequest(sender: object2){ (result) in
//Result - What I want to test (Write UnitTest about)
}
}
}
Use mocking.
class ServiceCallerMock: ServiceCaller {
override class func performRequest(route: ServiceRouter) -> (Any?) -> Void? {
//your implementation goes here
}
}
You could mock ServiceCaller and override the performRequest method, then change the function to:
static func initAppRequest(_ completion: #escaping (_ appInitRecevingModel: Result<AppInitRecevingModel>) -> Void, serviceCaller: ServiceCaller.Type = ServiceCaller.self) {
...
serviceCaller.performRequest(route: route) { (result) in
...
}
Then you could call the initAppRequest function using your mock implementation of ServiceCaller.
I'm using the moya library to make a POST request. In TargetType, I couldn't able to see any property to pass the parameters[JSON body] along with POST request. Here, I attach the TargetType
public protocol TargetType {
/// The target's base `URL`.
var baseURL: URL { get }
/// The path to be appended to `baseURL` to form the full `URL`.
var path: String { get }
/// The HTTP method used in the request.
var method: Moya.Method { get }
/// Provides stub data for use in testing.
var sampleData: Data { get }
/// The type of HTTP task to be performed.
var task: Task { get }
/// Whether or not to perform Alamofire validation. Defaults to `false`.
var validate: Bool { get }
/// The headers to be used in the request.
var headers: [String: String]? { get }
}
public extension TargetType {
var validate: Bool {
return false
}
}
Finally,I got the solution for my problem. In Moya 10.0, we can pass the http body JSON payload in task property [TargetType].
click here for reference
var task: Task {
switch self {
case .zen, .showUser, .showAccounts: // Send no parameters
return .requestPlain
case let .updateUser(_, firstName, lastName): // Always sends parameters in URL, regardless of which HTTP method is used
return .requestParameters(parameters: ["first_name": firstName, "last_name": lastName], encoding: URLEncoding.queryString)
case let .createUser(firstName, lastName): // Always send parameters as JSON in request body
return .requestParameters(parameters: ["first_name": firstName, "last_name": lastName], encoding: JSONEncoding.default)
}
}
I am adopting the MVVM pattern in my iOS application. I expose a range of Observables as public properties in my view model and bind the UI to these properties. These Observables are created from a private connectable observable.
A view controller class then calls the "execute" method to fire the network request. However, if it fails for any reason, I'd like to be able to call "execute" again but this does not work. I believe this is due to the fact that the connectable observable has completed.
How can I achieve this without having to recreate the view model each time? I know I could do this by transforming a simple execute publish subject to the userDetailsObservable by using flatMap but I rely on the onCompleted event for other functionality. The onCompleted event would be lost as the publish subject remains active.
Connectable Observable Solution
class ViewModel {
public var userName: Observable<String> {
self.userDetailsObservable.map {
return $0["username"]
}
}
public var address: Observable<String> {
self.userDetailsObservable.map {
return $0["address"]
}
}
public func execute() {
self.userDetailsObservable.connect()
}
private lazy var userDetailsObservable: ConnectableObservable<JSON> {
return Observable.create { observer in
// execute network request
// assume there is a json object and error object returned
if error != nil {
observer.onError(error)
} else {
observer.onNext(json)
}
observer.onCompleted()
}.publish()
}
}
The FlatMap solution
This would execute the network request every time an event is pushed on the execute subject. (execute.onNext()). The problem here is that the onCompleted event is lost as we are transforming a publish subject.
class ViewModel {
public var userName: Observable<String> {
self.userDetailsObservable.map {
return $0["username"]
}
}
public var address: Observable<String> {
self.userDetailsObservable.map {
return $0["address"]
}
}
public var execute: PublishSubject<Void>()
private lazy var userDetailsObservable: Observable<JSON> {
return self.execute.flatMapLatest { _ in
Observable.create { observer in
// execute network request
// assume there is a json object and error object returned
if error != nil {
observer.onError(error)
} else {
observer.onNext(json)
}
observer.onCompleted()
}
}.share()
}
You should use catchError and return a default value ("" for instance).
It’s required to prevent the observable from being disposed when you receive an error from the API.
Is there a way to call an async function from a lazy or computed property?
struct Item {
lazy var name: String = {
API.requestThing({ (string: String) in // Xcode didn't like this
return string // this would not work here
})
}()
}
class API {
class func requestThing(completion: String -> Void) {
completion("string")
}
}
Your completion handler in API.requestThing returns a String, yet it is supposed to have no return value:
(completion: String -> Void)
I got this to work:
struct Item {
lazy var name: String = {
API.requestThing({ (string: String) in
return string
})
}()
}
class API {
class func requestThing(completion: String -> String) -> String {
return completion("string")
}
}
There is no good reason to use "lazy" in this case. lazy is for initialization. Just create a normal func and pass a completion handler.
First, requestThing returns () (ie void) and not String. So the type of the following expression is also () and not String:
API.requestThing { string in
return string
}
Second, the call to requestThing is asynchronous, so even if you defined name as a lazy var, the call to the var body function is still synchronousand will return immediately.
So if you can transform name into a function like this:
func name(completion: String -> ()) {
API.requestThing { string in
completion(string)
}
}
// Later you call it in this way
myItem.name { name in
// use the value of name
}
If in addition you want to cache the retrieved value you can modify Item to a class and use the following code
class Item {
private var nameValue: String?
func name(completion: String -> ()) {
if let value = nameValue {
// return cached value
return completion(value)
} else {
// request value
API.requestThing { string in
// cache retrieved value
self.nameValue = string
// return retrieved value
completion(string)
}
}
}
}
There's probably no compelling reason to do this, but the following approach seems to be reasonable:
Instead having a variable of type String - we sometimes require a "Future" of that thing, e.g. Future<String>. A future represents the eventual result of an asynchronous operation - that is exactly what's given in your question.
The future itself is a "normal" variable and can be lazy evaluated, too. It just doesn't yet have its eventual value. That means, the underlying task will only be started when explicitly requested (e.g. lazily). From a design or architectural point of view, this may make sense.
func fetchString() -> Future<String> { ... }
lazy var name: Future<String> = fetchString()
Later in your code, you obtain the variable as follows:
item.name.map { string in
print(string)
}
If this is the first access to the lazy property, it will start the underlying asynchronous operation which calculates the string. Then, when the variable is available, the mapping function as provided in the map function will be called with the variable as an argument - possibly some time later, too.
Otherwise (if this is not the first access), it will just provide the string in the parameter when it is available, possibly immediately.
Since operations may fail, a "Future" also provides means to handle this:
item.name.map { string in
print(string)
}.onFailure { error in
print("Error: \(error)")
}
See also: https://en.wikipedia.org/wiki/Futures_and_promises
There are implementations for Futures in Swift and Objective-C, also often called "Promise".