Unit Testing iOS App which used Alamofire for network request - ios

I am new to iOS Development and I am building an app which uses Alamofire for HTTP request. I need to write Unit test for few things like login and need help with it. I did some research about Stub, Mocks and also read about few frameworks like Nocilla and OHTTPStubs but I am unable to get started. I have a login controller which gets login information like - username, password from storyboard and on login button action I call a server utility method. I have a server utility class which implements the method for making network request.
#IBAction func logIn(sender: AnyObject?) {
server.loginWith(username, password: password, server:url) { (user, error) in
dispatch_async(dispatch_get_main_queue()) {
if let err = error {
self.handleError(err)
return
}
self.performSegueWithIdentifier("unwind", sender: self)
}
}
}
This is server utility class
class Server {
func loginWith(username:String, password:String, server:String, callback:CompleteHandler) {
Alamofire.request(.GET, server + "/login", headers:defaultHeaders).validate().responseJSON {response in
var err:NSError?
var user:Object?
defer {
dispatch_group_leave(self.dispGrp)
callback(user:user, error: err)
}
switch response.result {
case .Success():
//gives me user data
case .Failure(let error):
// gives me error
}
}
How do I Unit test the following code provided I do not wish to make a network call. Please help me as I am stuck since long.
Thank you.

use OHHTTPStubs
Its allow you to :
test your apps with fake network data (stubbed from file) and simulate slow networks, to check your application behavior in bad network conditions
write unit tests that use fake network data from your fixtures.
And support swift 3.0 too

Related

How to customize Amplify Auth Error Messages

I am attempting to implement amplify auth on iOS, and what I would like to be able to do is customize the error message that is displayed to a user when authentication fails, as the default error messages are not end-user friendly, but I have no idea how to do this.
For instance, my signIn method is as follows:
func signIn(username: String) {
Amplify.Auth.signIn(username: username, password: "bla") { [weak self] result in
switch result {
case .success (let result):
if case .confirmSignInWithCustomChallenge(_) = result.nextStep {
DispatchQueue.main.async {
self?.showConfirmationSignInView()
}
} else {
print("Sign in succeeded")
}
case .failure(let error):
print (error)
}
}
}
Now in the .failure case, instead of printing the error, I would ideally like to determine if the error is a userNotFound error, or something else. I can't find any info in the docs on this. Any help would be appreciated.
You can do it by checking the error.code. for example, for a user who did not confirm the email if he tries to login then error.code will have UserNotConfirmedException string value. Amplify auth returns different exception codes for different types of errors. You can see all the exceptions from this link. Although it is for flutter, the exception code is identical for any framework. I have used these exception codes in react.

What is the best practice to deal with RxSwift retry and error handling

I read some post says that the best practice to deal with RxSwift is to only pass fatal error to the onError and pass Result to the onNext.
It makes sense to me until I realise that I can't deal with retry anymore since it only happen on onError.
How do I deal with this issue?
Another question is, how do I handle global and local retry mixes together?
A example would be, the iOS receipt validation flow.
1, try to fetch receipt locally
2, if failed, ask Apple server for the latest receipt.
3, send the receipt to our backend to validate.
4, if success, then whole flow complete
5, if failed, check the error code if it's retryable, then go back to 1.
and in the new 1, it will force to ask for new receipt from apple server. then when it reaches 5 again, the whole flow will stop since this is the second attempt already. meaning only retry once.
So in this example, if using state machine and without using rx, I will end up using state machine and shares some global state like isSecondAttempt: Bool, shouldForceFetchReceipt: Bool, etc.
How do I design this flow in rx? with these global shared state designed in the flow.
I read some post says that the best practice to deal with RxSwift is to only pass fatal error to the onError and pass Result to the onNext.
I don't agree with that sentiment. It is basically saying that you should only use onError if the programmer made a mistake. You should use errors for un-happy paths or to abort a procedure. They are just like throwing except in an async way.
Here's your algorithm as an Rx chain.
enum ReceiptError: Error {
case noReceipt
case tooManyAttempts
}
struct Response {
// the server response info
}
func getReceiptResonse() -> Observable<Response> {
return fetchReceiptLocally()
.catchError { _ in askAppleForReceipt() }
.flatMapLatest { data in
sendReceiptToServer(data)
}
.retryWhen { error in
error
.scan(0) { attempts, error in
let max = 1
guard attempts < max else { throw ReceiptError.tooManyAttempts }
guard isRetryable(error) else { throw error }
return attempts + 1
}
}
}
Here are the support functions that the above uses:
func fetchReceiptLocally() -> Observable<Data> {
// return the local receipt data or call `onError`
}
func sendReceiptToServer(_ data: Data) -> Observable<Response> {
// send the receipt data or `onError` if the server failed to receive or process it correctly.
}
func isRetryable(_ error: Error) -> Bool {
// is this error the kind that can be retried?
}
func askAppleForReceipt() -> Observable<Data> {
return Observable.just(Bundle.main.appStoreReceiptURL)
.map { (url) -> URL in
guard let url = url else { throw ReceiptError.noReceipt }
return url
}
.observeOn(ConcurrentDispatchQueueScheduler(qos: .userInitiated))
.map { try Data(contentsOf: $0) }
}

iOS/Swift: Good architecture approach for connecting REST APIs

I’m developing iOS Apps for quite a long time now. But in the end I was never satisfied with the architecture design for my network layer. Especially when it goes about connecting an API.
There exists a possible duplicate here, but I think my question is more specific as you will see.
Best architectural approaches for building iOS networking applications (REST clients)
I’m not looking for answers like "use AFNetworking/Alamofire". This question is regardless of which 3rd party framework is used.
I mean, often we have the scenario:
"Develop an app X that uses API Y"
And this includes mainly the same steps - everytime.
Implement login / registration
You get an authentication token, have to save it in the keychain and append it in every API call
You have to re-authenticate and re-send the API request which failed with a 401
You have error codes to handle (how to handle them centralized?)
You implement the different API calls.
One problem with 3)
In Obj-C I used NSProxy for intercepting every API Call before it was send, re-authenticated the user if the token expired and and fired the actual request.
In Swift we had some NSOperationQueue where we queued an auth call if we got a 401 and queued the actual request after successful refresh. But that limited us to use a Singleton (which I don’t like much) and we also had to limit the concurrent requests to 1.
I like more the second approach - but is there a better solution?
Regarding 4)
How do you handle http status codes? Do you use many different classes for every error? Do you centralize general error handling in one class? Do you handle them all at the same level or do you catch server errors earlier? (Maybe in your API Wrapper of any 3rd party lib)
How are you developers trying to solve this problems? Have you figured out a "best match" design?
How do you test your APIs? Especially how do you do this in Swift (with no real mocking possibility?).
Of course: Every use case, every app, every scenario is different - there is no "One solution fits them all". But I think these general problems re-appear so often, so I’m tempted to say "Yes, for these cases - there could be one and more solutions - which you can reuse every time".
Looking forward to interesting answers!
Cheers
Orlando 🍻
But that limited us to use a Singleton (which I don’t like much) and we also had to limit the concurrent requests to 1. I like more the second approach - but is there a better solution?
I am using a few layers for authenticating with an API.
Authentication Manager
This manager is responsible for all authentication related functionality. You can think about authentication, reset password, resend verification code functions, and so on.
struct AuthenticationManager
{
static func authenticate(username:String!, password:String!) -> Promise<Void>
{
let request = TokenRequest(username: username, password: password)
return TokenManager.requestToken(request: request)
}
}
In order to request a token we need a new layer called the TokenManager, which manages all things related to a token.
Token Manager
struct TokenManager
{
private static var userDefaults = UserDefaults.standard
private static var tokenKey = CONSTANTS.userDefaults.tokenKey
static var date = Date()
static var token:Token?
{
guard let tokenDict = userDefaults.dictionary(forKey: tokenKey) else { return nil }
let token = Token.instance(dictionary: tokenDict as NSDictionary)
return token
}
static var tokenExist: Bool { return token != nil }
static var tokenIsValid: Bool
{
if let expiringDate = userDefaults.value(forKey: "EXPIRING_DATE") as? Date
{
if date >= expiringDate
{
return false
}else{
return true
}
}
return true
}
static func requestToken(request: TokenRequest) -> Promise<Void>
{
return Promise { fulFill, reject in
TokenService.requestToken(request: request).then { (token: Token) -> Void in
setToken(token: token)
let today = Date()
let tomorrow = Calendar.current.date(byAdding: .day, value: 1, to: today)
userDefaults.setValue(tomorrow, forKey: "EXPIRING_DATE")
fulFill()
}.catch { error in
reject(error)
}
}
}
static func refreshToken() -> Promise<Void>
{
return Promise { fulFill, reject in
guard let token = token else { return }
let request = TokenRefresh(refreshToken: token.refreshToken)
TokenService.refreshToken(request: request).then { (token: Token) -> Void in
setToken(token: token)
fulFill()
}.catch { error in
reject(error)
}
}
}
private static func setToken (token:Token!)
{
userDefaults.setValue(token.toDictionary(), forKey: tokenKey)
}
static func deleteToken()
{
userDefaults.removeObject(forKey: tokenKey)
}
}
In order to request a token we'll need a third layer called TokenService which handles all the HTTP calls. I use EVReflection and Promises for my API calls.
Token Service
struct TokenService: NetworkService
{
static func requestToken (request: TokenRequest) -> Promise<Token> { return POST(request: request) }
static func refreshToken (request: TokenRefresh) -> Promise<Token> { return POST(request: request) }
// MARK: - POST
private static func POST<T:EVReflectable>(request: T) -> Promise<Token>
{
let headers = ["Content-Type": "application/x-www-form-urlencoded"]
let parameters = request.toDictionary(.DefaultDeserialize) as! [String : AnyObject]
return POST(URL: URLS.auth.token, parameters: parameters, headers: headers, encoding: URLEncoding.default)
}
}
Authorization Service
I am using an Authorisation Service for the problem you are describing here. This layer is responsible for intercepting server errors such as 401 (or whatever code you want to intercept) and fix them before returning the response to the user. With this approach everything is handled by this layer and you don't have to worry about an invalid token anymore.
In Obj-C I used NSProxy for intercepting every API Call before it was send, re-authenticated the user if the token expired and and fired the actual request. In Swift we had some NSOperationQueue where we queued an auth call if we got a 401 and queued the actual request after successful refresh. But that limited us to use a Singleton (which I don’t like much) and we also had to limit the concurrent requests to 1. I like more the second approach - but is there a better solution?
struct AuthorizationService: NetworkService
{
private static var authorizedHeader:[String: String]
{
guard let accessToken = TokenManager.token?.accessToken else
{
return ["Authorization": ""]
}
return ["Authorization": "Bearer \(accessToken)"]
}
// MARK: - POST
static func POST<T:EVObject> (URL: String, parameters: [String: AnyObject], encoding: ParameterEncoding) -> Promise<T>
{
return firstly
{
return POST(URL: URL, parameters: parameters, headers: authorizedHeader, encoding: encoding)
}.catch { error in
switch ((error as NSError).code)
{
case 401:
_ = TokenManager.refreshToken().then { return POST(URL: URL, parameters: parameters, encoding: encoding) }
default: break
}
}
}
}
Network Service
The last part will be the network-service. In this service layer we will do all interactor-like code. All business logic will end up here, anything related to networking. If you briefly review this service you'll note that there is no UI-logic in here, and that's for a reason.
protocol NetworkService
{
static func POST<T:EVObject>(URL: String, parameters: [String: AnyObject]?, headers: [String: String]?, encoding: ParameterEncoding) -> Promise<T>
}
extension NetworkService
{
// MARK: - POST
static func POST<T:EVObject>(URL: String,
parameters: [String: AnyObject]? = nil,
headers: [String: String]? = nil, encoding: ParameterEncoding) -> Promise<T>
{
return Alamofire.request(URL,
method: .post,
parameters: parameters,
encoding: encoding,
headers: headers).responseObject()
}
}
Small Authentication Demo
An example implementation of this architecture would be a authenticate HTTP request to login a user. I'll show you how this is done using the architecture described above.
AuthenticationManager.authenticate(username: username, password: password).then { (result) -> Void in
// your logic
}.catch { (error) in
// Handle errors
}
Handling errors is always a messy task. Every developer has it's own way of doing this. On the web there are heaps of articles about error handling in for example swift. Showing my error handling will be of not much help since it's just my personal way of doing it, it's also a lot of code to post in this answer, so I rather skip that.
Anyway...
I hope I've helped you back on track with this approach. If there is any question regarding to this architecture, I'll be more than happy to help you out with it. In my opinion there is no perfect architecture and no architecture that can be applied to all projects.
It's a matter of preference, project requirements and expertise in within your team.
Best of luck and please do no hesitate to contact me if there's any problem!

How do I run a Cloud Code on Heroku?

With the Parse's announcement of their retirement, I have migrated my Parse Server onto Heroku. With my still neophyte knowledge of Heroku, I do not know if they have a similar function to that of Cloud Code, but I do know that a few months ago Parse Introduced a Heroku + Parse feature that allows you to run Cloud Code on any node.js environment, particularly Heroku.
My dilemma is, I have already migrated my server from parse to Heroku prior to learning about this feature :/ , so I cannot run any parse cloud code form my terminal because there is no existing server there anymore. So the question is, how can I emulate this following Cloud Code in Heroku & How do I adjust my swift?
Cloud Code:
// Use Parse.Cloud.define to define as many cloud functions as you want.
// For example:
Parse.Cloud.define("isLoginRedundant", function(request, response) {
Parse.Cloud.useMasterKey();
var sessionQuery = new Parse.Query(Parse.Session);
sessionQuery.equalTo("user", request.user);
sessionQuery.find().then(function(sessions) {
response.success( { isRedundant: sessions.length>1 } );
}, function(error) {
response.error(error);
});
});
and here is my swift back in xcode:
PFUser.logInWithUsernameInBackground(userName!, password: passWord!) {
(user, error) -> Void in
if (user != nil) {
// don't do the segue until we know it's unique login
// pass no params to the cloud in swift (not sure if [] is the way to say that)
PFCloud.callFunctionInBackground("isLoginRedundant", withParameters: [:]) {
(response: AnyObject?, error: NSError?) -> Void in
let dictionary = response as! [String:Bool]
var isRedundant : Bool
isRedundant = dictionary["isRedundant"]!
if (isRedundant) {
// I think you can adequately undo everything about the login by logging out
PFUser.logOutInBackgroundWithBlock() { (error: NSError?) -> Void in
// update the UI to say, login rejected because you're logged in elsewhere
// maybe do a segue here?
let redundantSession: String = "you are already logged in on another device"
self.failedMessage(redundantSession)
self.activityIND.stopAnimating()
self.loginSecond.userInteractionEnabled = true
}
} else {
// good login and non-redundant, do the segue
self.performSegueWithIdentifier("loginSuccess", sender: self)
}
}
} else {
// login failed for typical reasons, update the UI
dispatch_async(dispatch_get_main_queue()) {
self.activityIND.stopAnimating()
self.loginSecond.userInteractionEnabled = true
if let message = error?.userInfo["error"] as? String
where message == "invalid login parameters" {
let localizedMessage = NSLocalizedString(message, comment: "Something isn't right, check the username and password fields and try again")
print(localizedMessage)
self.failedMessage(localizedMessage)
}else if let secondMessage = error?.userInfo["error"] as? String
where secondMessage == "The Internet connection appears to be offline." {
self.failedMessage(secondMessage)
}
}
}
}
I would first checkout the example repo and read the parse-server documentation. Parse server supports cloud code out of the box and you simply specify which file contains your functions and triggers in the parse-server config. The link you posted with the integration between parse and heroku is not relevant for parse-server.

how to correctly make cloud code request with parse?

I am attempting to make all my user sessions with Parse exclusive, meaning if a user is already logged in on a certain device in a certain location, if another device logs in with the same credentials, I want the previous session(s) to be terminated, with a message of an alert view of course. Sort of like the old AOL Instant Messaging format. I figured the code for this action should be written in the login logic, so I wrote this within my login "succession" code :
PFUser.logInWithUsernameInBackground(userName, password: passWord) {
(user, error: NSError?) -> Void in
if user != nil || error == nil {
dispatch_async(dispatch_get_main_queue()) {
self.performSegueWithIdentifier("loginSuccess", sender: self)
PFCloud.callFunctionInBackground("currentUser", withParameters: ["PFUser":"currentUser"])
//..... Get other currentUser session tokens and destroy them
}
} else {
Thats probably not the correct cloud code call, but you get the point. When the user logs in once again on another device, I want to grab the other sessions and terminate them. Does anyone know the correct way to go about making this request in swift?
I speak swift with a stutter, but I think I can answer adequately in almost-swift. The key idea is to start the success segue only after the cloud says it's okay. Here's what I think you want:
PFUser.logInWithUsernameInBackground(userName, password: passWord) {
(user, error: NSError?) -> Void in
if (user != nil) {
// don't do the segue until we know it's unique login
// pass no params to the cloud in swift (not sure if [] is the way to say that)
PFCloud.callFunctionInBackground("isLoginRedundant", withParameters: []) {
(response: AnyObject?, error: NSError?) -> Void in
let dictionary = response as! [String:Bool]
var isRedundant : Bool
isRedundant = dictionary["isRedundant"]!
if (isRedundant) {
// I think you can adequately undo everything about the login by logging out
PFUser.logOutInBackgroundWithBlock() { (error: NSError?) -> Void in
// update the UI to say, login rejected because you're logged in elsewhere
// maybe do a segue here?
}
} else {
// good login and non-redundant, do the segue
self.performSegueWithIdentifier("loginSuccess", sender: self)
}
}
} else {
// login failed for typical reasons, update the UI
}
}
Please don't take me too seriously on swift syntax. The idea is to nest the segue in the completion handlers to know that you need to do it before starting it. Also, please note that the explicit placement on the main_queue within the completion handler is unnecessary. The SDK runs those blocks on the main.
A simple check to determine if a user's session is redundant (not unique) looks like this...
Parse.Cloud.define("isLoginRedundant", function(request, response) {
var sessionQuery = new Parse.Query(Parse.Session);
sessionQuery.equalTo("user", request.user);
sessionQuery.find().then(function(sessions) {
response.success( { isRedundant: sessions.length>1 } );
}, function(error) {
response.error(error);
});
});

Resources