Im unit testing my Alamofire code at the moment. Im trying to create a reusable method that I can pass in a status code and a method that will need to be invoked as parameters. I was thinking of using a closing block but the syntax is escaping me and it doesnt help that I have started with Swift. Here is an example:
func parseJsonResponse(response: Response <AnyObject, NSErrror>){
//parsing
}
func test_networkRequest(withStatusCode statusCode: Int, /* parse function as parameter */) {
stub(isHost("https://httpbin.org")) { _ in
return OHHTTPStubsResponse(fileAtPath:OHPathForFile("wsresponse.json", self.dynamicType)!,
statusCode:statusCode, headers:["Content-Type":"application/json"])
})
Alamofire.request(.GET, "https://httpbin.org/get", parameters: ["foo": "bar"])
.responseJSON { response in
print(response.request) // original URL request
print(response.response) // URL response
print(response.data) // server data
print(response.result) // result of response serialization
XCTAsertNotNil(response)
if let JSON = response.result.value {
print("JSON: \(JSON)")
}
//method should go here
self.parseJsonResponse(response)
}
}
I want to be able to make 'test_networkRequest' reusable in all my classes, and there will be different types of json to be parsed and handled in various ways. This is why I would like to be able to pass in a function as a parameter into 'test_networkRequest'.
I hope my end goal is clear, and I am open to suggestions if I am off track. :)
To create a block, do something like this:
class Foo {
var block: (statusCode: Int) -> Void
var optionalBlock: ((argA: Int, argB: String) -> Bool)? // in case you're curious
init() {
block = { (statusCode: Int) -> Void in
// This is the block that can now be passed
}
}
}
You may want to think about your decision to use a block though. From the sentence "I want to be able to make 'test_networkRequest' reusable in all my classes..." it sounds like this may be better as a function in a superclass.
Related
Lets say I have something like this
import Alamofire
class NetworkStuff {
func getSomething() async throws -> String {
let params: [String: String] = ["Key": "Value"]
let value = AF.request("https://myapi.com/endpoint", method: .post, parameters: params, encoder: JSONParameterEncoder.default)
.validate()
.responseDecodable([String: String].self).value("KeyIWant")
guard let result = value else {throw MyError.SomeError }
return result
}
}
How would I go about writing a unit test to validate that this request is right and that im decoding the response.
Lets say we send this to the server:
{
"RequestItem": "Value"
}
Lets say the response from the server looks like:
{
"ID": "1234",
"KeyIWant": "Value02"
}
How could I write a test to ensure my POST parameters were structured correctly as the server expects and then mock a response so that I ensure im parsing this correctly to get the value I want?
Keep in mind that you do not want to test the network or AlamoFire or the server at the endpoint. You want to test only your code.
To do that, you need to make your code testable. Break out getSomething into subroutines that are testable — this sort of thing:
func getSomething() async throws -> String {
let params = self.constructParams()
let value = AF.request("https://myapi.com/endpoint", method: .post, parameters: params, encoder: JSONParameterEncoder.default)
let result = self.processValue(value)
return result
}
Now constructParams and processValue are methods that you can test by normal means, providing various inputs and checking to see that you get back the expected outputs.
I will break the answer into two parts (I assume you are familiar with XCTest framework or its open-source wrappers, Quick and Nimble, those are my recommendation to be used for testing):
First, writing a test to make sure the POST parameters are structured correctly - I would reconstruct the function and pass them as an argument to it. Then I would write a class that is in charge of building the parameters and write a testing suite for it - making sure it builds the parameters correctly, something like
final class ParamBuilder {
func params() -> [String: Any] { ["Key": "Value"] }
}
// Testing
class ParamBuildierTests: XCTestCase {
func testFirst() {
let builder = ParamBuilder()
XCTAssertEqual(builder.params(), ["Key": "Value"])
}
}
Second, mock and test response.
I personally recommend using Mockingjay Its usage is pretty straightforward, and it can mock response such that you can keep using the same service NetworkStuff which is using Alamofire (URLSession underlying). It will stub the response according to your preference - you can mock 404 response and different payloads
I'm trying to serialize some JSON data that is coming from an API endpoint, and because all of the endpoints would have the same structure I thought it would be best for me to implement something that doesn't need to get repeated again in code.
JSON response 1:
{
"code":1100,
"message":"Successfully created application",
"data":{
"key":116541
}
}
JSON response 2:
{
"code":1101,
"message":"Successfully retrived",
"data":{
"id":116541,
"name":"hallow"
}
}
only data changes depending on the API endpoint. They would always come in this structure tho.
struct RawResponse<T: Decodable>: Decodable {
let code: Int
let message: String
let data: T
}
This is where I don't know how to decode RawResponse using DecodableResponseSerializer from Alamofire.
final class CustomDecodeableResponseSerializer<T: Decodable>: ResponseSerializer {
//...decoder setup
//don't work, missing "T" because it's defined in the struct don't know where to add this
private lazy var successSerializer = DecodableResponseSerializer<RawResponse>(decoder: decoder)
//This works as it's just pure struct without generic
private lazy var errorSerializer = DecodableResponseSerializer<APIError>(decoder: decoder)
//...public func serialize stuff
}
This would not work, because it's asking for that "T" defined in the struct, and "T" is passed in through an extension to DataRequest
#discardableResult
func responseCustom<T: Decodable>(queue: DispatchQueue = DispatchQueue.global(qos: .userInitiated), of t: T.Type, completionHandler: #escaping (Result<T, APIError>) -> Void) -> Self {
return response(queue: .main, responseSerializer: CustomDecodeableResponseSerializer<T>()) { response in
switch response.result {
case .success(let result):
completionHandler(result)
case .failure(let error):
completionHandler(.failure(APIError(code: -1, message: error.localizedDescription)))
}
}
}
So it can be called like this:
User is another struct for JSON "data" field to be decoded as
session.request()
.validate()
.responseCustom(of: User.self){(response) in
//do stuff
}
I hope this makes sense...I know that if I just pass T instead of making a raw response struct, I can just repeat code and message in every response struct, and it will work. But I'm seeking to not repeat code and message throughout every response struct. Or is there is a simpler way to achieve this?
You don't have it in your code, but make sure your CustomDecodeableResponseSerializer is returning RawResponse<T> from its serialize method. Then you can make sure your decoder for the success case is DecodableResponseSerializer<RawResponse<T>>(decoder: decoder).
Also, there's no reason to use DispatchQueue.global(). That parameter just controls where the completion handler is called, the actual serialization work is always performed in the background.
So I have this enum that I use for the few url requests I use in my app :
enum Netwrok {
case popular
case topRated
case latest
// ...
static let baseUrl = "http://..."
func path() -> String {
switch self {
case .popular: return "/popular"
// ...
}
}
}
And I would like to add a function that returns the Decodable Type of model the network stack should decode the data with.
So I thought something like that would do the job :
func returnType<T>() -> T.Type where T : Decodable {
switch self {
case .popular:
return Popular.self
// ...
}
}
But I can't make it work, it says :
Cannot convert return expression of type 'Popular.Type' to return type 'T.Type'
Asking me to force cast in T.Type.
How can I make a function that returns the decodable so that type can be handled but the JSONDecoder's decode function ?
Thanks.
What you're asking is straightforward, but it probably isn't what you want. What you're asking to do is to return a type. There's nothing generic about that.
func returnType<T>() -> T.Type where T : Decodable {
This syntax defines a type parameter, T, that is passed by the caller. It's not defined by your function. That means the caller may pass any type that is Decodable and your function will return it. For example, the caller can set T to be Int (since that's Decodable), and you will return Int.Type. That's easy to implement (return T.self), but not what you mean.
What you mean is that the function returns some type that is Decodable that the function knows, but the caller doesn't:
func returnType() -> Decodable.Type { ... }
This will work fine, and do exactly what you are asking for, but it suggests you're probably building this network stack incorrectly and will have headaches later.
The reason this approach is likely to be a problem is that you probably want to write a line of code like this:
let result = JSONDecoder().decode(networkType.returnType(), from: data)
That's going to break, because Decodable.Type is not itself a Decodable type. (You you decode Int, but you can't decode the type of Int.) Say it did work. What type would result be? What could you do with it? The only thing you'd know about it is that it's Decodable (and you've already decoded it).
You likely want something more like Vasu Chand's implementation, or the similar approach discussed in my blog series.
You can use escaping closure for your returning result of an API Call.
Assuming you are hitting a get request . A simple working example for passing Codable model for get request api.
class func GETRequest<ResponseType :Decodable>(url : URL,responseType : ResponseType.Type ,completion: #escaping (ResponseType? ,Error? ) -> Void){
var request = URLRequest(url: url)
request.httpMethod = "GET"
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
guard let data = data else{
completion(nil,error)
return
}
let decoder = JSONDecoder()
do{
let responseData = try decoder.decode(ResponseType.self, from: data)
completion(responseData, nil)
}
catch let error{
completion(nil, error)
}
}
task.resume()
}
How to call this network function.
Network.GETRequest(url: url, responseType: Model.self) { (model, error) in
completion(model,error)
}
Model class contains
struct Model : Codable{
}
You can pass any response model for any get request to network class .
Similarly you can build api network for post request where request body is simply Codable model .
For sorry you can't as according to your need the supply for the first parameter here
JSONDecoder().decode(AdecodableType.self,from:data)
need to be inferred right when you write the code so it can't be Any 1 from a collection of types that conform to Decodable
I want to create a property on a class that uses the class type as a generic parameter, and I'm having difficulty working it out.
open class ResponseProcessor {
required public init() {
}
var success: ((_ responseProcessor: ResponseProcessor) -> Void)?
func process() {
success?(self)
}
}
class TestProcessor: ResponseProcessor {
var result: String?
override func process() {
result = "Some Result"
super.process()
}
}
open class Request<ResponseProcessorType: ResponseProcessor> {
var success: ((_ responseProcessor: ResponseProcessor) -> Void)?
func doRequest() {
let responseProcessor = ResponseProcessorType.init()
responseProcessor.success = success
responseProcessor.process()
}
}
class TestRequest: Request<TestProcessor> {
}
let testRequest = TestRequest()
testRequest.success = { (responseProcessor) in
// This line reports an error, but I want it to know what
// type the responseProcessor is.
print(responseProcessor.result)
}
testRequest.doRequest()
I want to be able to assign SubRequest to the .request variable, but I can't because of strict generic typing.
So I'd like to be able to say "the request property on a ResponseProcessor should be of type Request<WhateverThisClassIs>, but I can't work out how to express that, or declare it in a way that works.
It should work out that testProcessor.request is of type HTTPRequest<TestProcessor>, but obviously that isn't happening.
I'm not sure if this is going to answer your question or not, but maybe it will put you on a better road. To your stated question, the answer is there is no generic covariance in Swift. What you're trying to write is not possible. Generic covariance wouldn't actually fix your code, because you have a lot of other type problems here (your latest version is probably violating Liskov's Substitution Principle, which means it breaks the meaning of class inheritance). But I don't think you actually want what you're trying to write at all.
I suspect you're writing a pluggable and testable networking stack. That's really common. He's a fairly simple one; they can get much more powerful if you tear this apart a bit more.
First, the low-level networking stack itself should consume URLRequests and return Data. That's all. It should not try to deal with model types. This is where people always go off the rails. So a Request is an URLRequest and a completion handler:
struct Request {
let urlRequest: URLRequest
let completion: (Result<Data, Error>) -> Void
}
And a client consumes those.
final class NetworkClient {
func fetch(_ request: Request) {
URLSession.shared.dataTask(with: request.urlRequest) { (data, _, error) in
if let error = error { request.completion(.failure(error)) }
else if let data = data { request.completion(.success(data)) }
}.resume()
}
}
Now we generally don't want to talk to URLSession when we're testing. We want to throw back pre-canned data probably. So we make one of those.
final class TestClient {
enum ClientError: Error {
case underflow
}
var responses: [Result<Data, Error>]
init(responses: [Result<Data, Error>]) { self.responses = responses }
func fetch(_ request: Request) {
if let response = responses.first {
responses.removeFirst()
request.completion(response)
} else {
request.completion(.failure(ClientError.underflow))
}
}
}
I'm marking things final class because these are sensibly reference types, but I want to make it clear that I'm not using class inheritance anywhere here. (Feel free to leave "final" off in your own code; it's a bit pedantic and usually not needed.)
How are these two things alike? They share a protocol:
protocol Client {
func fetch(_ request: Request)
}
Great. Now I can do things like:
let client: Client = TestClient(responses: [])
No associated types means that Client is perfectly fine as a type.
But getting back Data is kind of ugly. We want a type, like User.
struct User: Codable, Equatable {
let id: Int
let name: String
}
How do we do that? We just need a way to construct a Request that fetches a Decodable:
extension Request {
init<Model: Decodable>(fetching: Model.Type,
from url: URL,
completion: #escaping (Result<Model, Error>) -> Void) {
self.urlRequest = URLRequest(url: url)
self.completion = { data in
completion(Result {
try JSONDecoder().decode(Model.self, from: data.get())})
}
}
}
Notice how Request still doesn't know anything about models? And Client doesn't know anything about models. There's just this Request initializer that takes a Model type and wraps it up in a way that can accept Data and spit back a Model.
You can take this approach miles further. You can write a Client that wraps a Client and modifies the request, adding headers for example.
struct AddHeaders: Client {
let base: Client
let headers: [String: String]
func fetch(_ request: Request) {
var urlRequest = request.urlRequest
for (key, value) in headers {
urlRequest.addValue(value, forHTTPHeaderField: key)
}
base.fetch(Request(urlRequest: urlRequest,
completion: request.completion))
}
}
let client = AddHeaders(base: NetworkClient(),
headers: ["Authorization": "Token ...."])
There are no subclasses here, no generic types, just one protocol (which has no associated types), and one generic method. But you can plug in a wide variety of back-ends, and compose together any operation that can be made to match one of a handful of transforms (Request -> Request, Request -> Data, Data -> Void).
I hope this matches some of what you're getting at with your question. Best of luck.
Hope you can help me. I want a swift function that make a post request and return the json data
so here is my class
import Foundation
class APICall {
//The main Url for the api
var mainApiUrl = "http://url.de/api/"
func login(username: String, password: String) -> String {
let post = "user=\(username)&password=\(password)";
let action = "login.php";
let ret = getJSONForPOSTRequest(action: action, post: post)
return ret;
}
//Function to call a api and return the json output
func getJSONForPOSTRequest(action: String, post: String) -> String {
var ret: String?
let apiUrl = mainApiUrl + action;
let myUrl = URL(string: apiUrl);
var request = URLRequest(url:myUrl!);
request.httpMethod = "POST";
let postString = post;
request.httpBody = postString.data(using: String.Encoding.utf8);
let task = URLSession.shared.dataTask(with: request) { (data: Data?, response: URLResponse?, error: Error?) in
if error != nil
{
print("error=\(error)")
return
}
print("response=\(response)")
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary
if let parseJSON = json {
let login = parseJSON["Login"] as? String
print("login: \(login)")
ret = login
}
} catch {
print(error)
}
}
task.resume()
return ret!;
}
}
But ret is nil. In the debugger is see the inner of the task is called later by another thread?
How can if fix that?
Thank you guys
The data task completion closure is called on another thread and after the execution of the method is completed so you need to re-jig your code a bit. Instead of having a String return value for your getJSONForPOSTRequest, don't return anything and instead have an additional argument that is a closure and call that from within your dataTask closure instead.
func getJSONForPOSTRequest(action: String, post: String, completion: (string: String) -> Void) {
// ...
let task = URLSession.shared.dataTask(with: request) { (data: Data?, response: URLResponse?, error: Error?) in
// ... (Convert data to string etc.)
completion(string: myString)
}
task.resume()
}
Remember, doing this means that the completion handler will be called once the network request completes and not right away.
EDIT:
Lets take this from the beginning. When you download something from the network in iOS you typically use NSURLSession. NSURLSession has a number of methods available to it for different means of interacting with the network, but all of these methods use a different thread, typically a background thread, which will do work independently of the rest of your code.
With this in mind, when you call the dataTask method you will notice that you have to add a completion closure as one of the parameters (notice in your example you are using something called a 'trailing closure' which is a closure that is the last argument in the method call that doesn't fall within the parenthesis of the method with the rest of the arguments). Think of a closure as a piece of code that is executed at a different time, it's not executed in line with the rest of the code around it (See the Swift documentation on closures here). In this case the closure will be called once the network request has been completed. Network requests aren't instant so we typically use a background thread to execute them while the user is shown an activity indicator etc and can still use the app. If we waited until the network request completed on the same thread as the rest of our code then it results in the app appearing laggy and even frozen which is terrible for users.
So going back to your example at hand; when you call your getJSONForPOSTRequest method the code within that method will complete and return before the network request has completed which is why we don't need to use a return value. Once the network request has completed your closure code will get called. Because the closure is called later it's also being called from an entirely different place within the code, in this case it's called from within iOS's network code. Because if this if you return a value from within the closure you will be trying to return the value to the network code which isn't what you want, you want to return the value to your own code.
To return the value of the network response to your code you need to define a closure (or a delegate, but I'm not going to go into that here) yourself. If you look at the example code above I've removed the return value from your getJSONForPOSTRequest method and added a new argument called 'completion', and if you look at the type of that argument you can see it's (string: String) -> Void, this defines a closure that passes in a string (the string that you will have downloaded from the network). Now that we have a closure thats within your method we can use this to call back to the caller of the getJSONForPOSTRequest with the data we have downloaded form the network.
Lets take your login method and see how we use getJSONForPOSTRequest within it:
func login(username: String, password: String, completion: (success: Bool) -> Void) {
let post = "user=\(username)&password=\(password)";
let action = "login.php";
let ret = getJSONForPOSTRequest(action: action, post: post) { string in
// This will be called once the network has responded and 'getJSONForPOSTRequest' has processed the data
print(string)
completion(success: true)
}
}
See that again we aren't returning anything directly from the login method as it has to rely on the a-synchronousness of calling off to the network.
It might feel by now that you are starting to get into something called 'callback hell', but this is the standard way to deal with networking. In your UI code you will call login and that will be the end of the chain. For example here is some hypothetical UI code:
func performLogin() {
self.activityIndicator.startAnimating()
self.apiCaller.login(username: "Joe", password: "abc123") { [weak self] success in
print(success)
// This will get called once the login request has completed. The login might have succeeded of failed, but here you can make the decision to show the user some indication of that
self?.activityIndicator.stopAnimating()
self?.loginCompleted()
}
}
Hopefully that clarifies a few things, if you have any other questions just ask.