How to mock response from AFNetworking with OCMock in Swift? - ios

This Is how i get the instance of my network client:
let networkClient = DBNetworkClient(baseURL: NSURL(string: "http://mysite.pl/api"))
I also have one method:
func citiesWithParameters(parameters: [String: String], completionBlock: DBSearchOptionHandler) {
GET("cities", parameters: parameters, success: { operation, response in
if let error = NSError(response: response) {
completionBlock([], error)
} else {
let cities = DBCity.parseCitiesWithDictionary(response as! NSDictionary)
completionBlock(cities, nil)
}
}) { operation, error in
completionBlock([], error)
}
}
This is how I call this method:
networkClient.citiesWithParameters(parameters, completionBlock: { cities, error in
//do sth
})
This way I pass some parameters, and get the REAL response from server. I would like to mock THAT response when I ask for this. How to do this?
func testCities() {
let mockNetworkClient = OCMockObject.mockForClass(DBNetworkClient.classForCoder())
//what should I perform here to be able to do sth like this:
let expectation = expectationWithDescription("")
mockNetworkClient.citiesWithParameters(["a": "b"]) { cities, error in
expectation.fulfill()
XCTAssertNotNil(cities)
XCTAssertNil(error) //since I know what is the response, because I mocked this
}
waitForExpectationsWithTimeout(10, handler: nil)
}
And this is how my method GET is defined within DBNetworkClient:
override func GET(URLString: String!, parameters: AnyObject!, success: ((AFHTTPRequestOperation!, AnyObject!) -> Void)!, failure: ((AFHTTPRequestOperation!, NSError!) -> Void)!) -> AFHTTPRequestOperation! {
return super.GET(URLString, parameters: parameters, success: { (operation, response) in
print("GET \(operation.request.URL)")
print("GET \(response)")
success(operation, response)
}, failure: { (operation, error) in
print("GET \(operation.request.URL)")
print("GET \(operation.responseObject)")
failure(operation, error)
})
}
Once I will be able I will award 50 bounty for the one, who help me do this.
Writing mock tests for AFNetworking is unfortunetaly not helpful. It is not working for me.

I do not have experience with Alamofire therefore I don't know where is declared your GET method but you should definitely stub this method instead of citiesWithParameters.
For example if it's declared in your DBNetworkClient:
func testCities()
{
//1
class DBNetworkClientMocked: DBNetworkClient
{
//2
override func GET(URLString: String!, parameters: AnyObject!, success: ((AFHTTPRequestOperation!, AnyObject!) -> Void)!, failure: ((AFHTTPRequestOperation!, NSError!) -> Void)!) -> AFHTTPRequestOperation! {
//3
success(nil, ["San Francisco", "London", "Sofia"])
}
}
//4
let sut = DBNetworkClientMocked()
sut.citiesWithParameters(["a":"b"]) { cities, error in
//5
XCTAssertNotNil(cities)
XCTAssertNil(error)
}
}
So what happens here:
You define class that is children to DBNetworkClient, therefore making a 'Mock' for it as described in article you posted. In that class we will override only methods that we want to change(stub) and leave others unchanged. This was previously done with OCMock and was called Partial Mock.
Now you stub it's GET method in order to return specific data, not actual data from the server
Here you can define what to return your stubbed server. It can be success failure etc.
Now after we are ready with mocks and stubs, we create so called Subject Under Test(sut). Please note that if DBNetworkClient has specific constructor you must call this constructor instead of default one - ().
We execute method that we want to test. Inside it's callback we put all our assertions.
So far so good. However if GET method is part of Alamofire you need to use a technique called Dependency Injection(you could google it for more info).
So if GET is declared inside another class and is only referenced in citiesWithParameters, how we can stub it? Let's look at this:
//1
class DBNetworkClient
{
//2
func citiesWithParameters(parameters: [String: String], networkWorker: Alamofire = Alamofire(), completionBlock: DBSearchOptionHandler) {
//3
networkWorker.GET("cities", parameters: parameters, success: { operation, response in
if let error = NSError(response: response) {
completionBlock([], error)
} else {
let cities = DBCity.parseCitiesWithDictionary(response as! NSDictionary)
completionBlock(cities, nil)
}
}) { operation, error in
completionBlock([], error)
}
}
}
func testCities()
{
//4
class AlamofireMock: Alamofire
{
override func GET(URLString: String!, parameters: AnyObject!, success: ((AFHTTPRequestOperation!, AnyObject!) -> Void)!, failure: ((AFHTTPRequestOperation!, NSError!) -> Void)!) -> AFHTTPRequestOperation! {
success(nil, ["San Francisco", "London", "Sofia"])
}
}
//5
let sut = DBNetworkClient()
sut.citiesWithParameters(["a":"b"], networkWorker: AlamofireMock()) { cities, error in
//6
XCTAssertNotNil(cities)
XCTAssertNil(error)
}
}
First we have to slightly change our citiesWithParameters to receive one more parameter. This parameter is our dependency injection. It is the object that have GET method. In real life example it will be better this to be only protocol as citiesWithParameters doesn't have to know anything more than this object is capable of making requests.
I've set networkWorker parameter a default value, otherwise you need to change all your call to citiesWithParameters to fulfill new parameters requirement.
We leave all the implementation the same, just now we call our injected object GET method
Now back in tests, we will mock Alamofire this time. We made exactly the same thing as in previous example, just this time mocked class is Alamofire instead of DBNetworkClient
When we call citiesWithParameters we pass our mocked object as networkWorker. This way our stubbed GET method will be called and we will get our expected data from 'fake' server.
Our assertions are kept the same
Please note that those two examples do not use OCMock, instead they rely entirely on Swift power! OCMock is a wonderful tool that we used in great dynamic language - Objective-C. However on Swift dynamism and reflection are almost entirely missing. Thats why even on official OCMock page we had following statement:
Will there be a mock framework for Swift written in Swift? Maybe. As
of now it doesn't look too likely, though, because mock frameworks
depend heavily on access to the language runtime, and Swift does not
seem to provide any.
The thing that is missing with in both implementations provided is verifying GET method is called. I'll leave it like this because that was not original question about, and you can easily implement it with a boolean value declared in mocked class.
One more important thing is that I assume GET method in Alamofire is instance method, not a class method. If that's not true you can declare new method inside DBNetworkClient which simply calls Alamofire.GET and use that method inside citiesWithParameters. Then you can stub this method as in first example and everything will be fine.

Related

PromiseKit no callback / deallocates? (Alamofire)

My promise chain is broken (maybe deallocated) before it's resolved.
This happens (so far ONLY) when I make Alamofire request fail due to host trust evaluation -> forcing evaluation to fail which results in -999 cancelled etc).
Setup is rather simple:
APIRequest:
func start(_ onSuccess:#escaping SuccessBlock, onError:#escaping ErrorBlock) {
do {
try executeRequest { dataResponse in
self.onSuccess(dataResponse)
}
} catch {
self.onError(error)
}
}
where executeRequest() is:
self.manager.request(request).responseJSON(queue: self.queue) { (response) in
completion(response)
}
Then, there is PromiseKit wrapper defined as APIRequest extension:
(This wrapper callbacks are called correctly in either case)
func start() -> Promise<APIResponse> {
return Promise<APIResponse> { resolver in
self.start({ response in
resolver.fulfill(response)
}) { error in
resolver.reject(error)
}
}
}
And finally, unit test calling the start promise (extension):
( flow never reaches this place in case of Alamofire failing )
request.start().done { response in
}.catch { error in
// not called if request failed
}
Outcome: if request fails -> the extension promise wrapper (catch) block is called, but it's not propagated back to UnitTest promise block.
Simply replacing Alamofire request with mock implementation (which triggers some other error( makes all setup work as expected (Unit Test completes with catch block being called etc) ex:
DispatchQueue.global(qos: .default).asyncAfter(deadline: .now() + 2) {
let result = Alamofire.Result { () -> Any in
return try JSONSerialization.data(withJSONObject: [:], options: .fragmentsAllowed)
}
completion(DataResponse<Any>(request: nil, response: nil, data: nil, result: result))
}
Is this something to do with Alamofire? I don't really see how else to handle the promises there ( they shouldn't deallocate anyways... Or is this bug in PromiseKit? Alamofire? I yet have to test this in the app itself ( maybe it's Unit test issue ... )
Looking at related issues -> i'm definitely resolving promises (fulfilling / rejecting) for any flow path.
I don't see how Alamofire request is different from DispatchAsync (where the latter works as expected).
I was only 10 mins short of finding the answer... Problem is also described here:
https://github.com/mxcl/PromiseKit/issues/686
Issue is that '-999 cancelled' error is not treated as 'Error' by PromiseKit. Solution is to use "catch(policy: .allErrors)" - then catch block is called as expected.
func start(_ onSuccess:#escaping SuccessBlock, onError:#escaping ErrorBlock) {
do {
try executeRequest { dataResponse in
onSuccess(dataResponse)
}
} catch {
onError(error)
}
}
You are using self.onSuccess that means its not function parameter block but instance block thats why its not going back to block from you are calling start.

Crash when accessing nested completion block -- EXC_BAD_ACCESS

I've found a reliable crash with EXC_BAD_ACCESS while implementing a cache on my app. I've recreated the situation in a new project in about ~50 lines of code with 2 pods (Alamofire, HanekeSwift), and it crashes every time.
All I am doing is in my ViewController, calling a cache get:
CachingManager.sharedInstance.fetchAllThings({
result in
// result should be an array of objects
print(result)
})
and the CachingManager's fetchAllThings method is:
func fetchAllThings(completion: ([AnyObject] -> Void)?) {
let fetcher = CustomFetcher<JSON>(key: "HELLO")
fetcher.fetch(failure: {
error in
print(error)
}, success: {
json in
completion?(json.array)
})
}
Now, the CustomFetcher found above is implemented as such:
override func fetch(failure fail: ((NSError?) -> ()), success succeed: (JSON) -> ()) {
let endpoint = "www.google.com"
Alamofire.request(.GET, endpoint, parameters: nil, encoding: .URL, headers: nil)
.responseJSON {
(data) -> Void in
self.onReceiveData(data.data!, failure: fail, success: succeed)
}
}
private func onReceiveData(data: NSData, failure: ((NSError?) -> ()), success: (JSON) -> ()) {
success(JSON.Array(["HI"])) <--- CRASH HAPPENS HERE
}
I get an EXC_BAD_ACCESS when trying to run the success block in my onReceiveData.
Am I doing something wrong? Is this a Swift issue? Is this an issue with the pods I'm using?
For reference, linked is a project with the code above that will crash on launch every time if that helps anyone debug this.
Project on GitHub
Seems like the use of a Custom fetcher is not that clear in the Haneke docs. With the above fetchAllThings method, I was trying to fetch directly from the CustomFetcher, which was causing the bad access (that is not it's intended use, I am guessing). The correct way to use a fetcher with a JSON cache is:
func fetchAllThings(completion: ([AnyObject] -> Void)?) {
let fetcher = CustomFetcher<JSON>(key: "HELLO")
let cache = Shared.JSONCache
cache.fetch(fetcher: fetcher, formatName: "original", failure: {
error in
print(error)
}, success: {
json in
completion?(json.array)
})
}
The key here is that I am now using the library's cache object to invoke the Fetcher.
I hope this helps someone!

Testable Asynchronous Design Pattern in Swift

I'm learning Test Driven Development in Swift. I hit a wall when I realized the delegate pattern I regularly use for asynchronous requests is difficult to test. I've learned that if something's difficult to test, the design pattern behind the implementation could probably be better. This is confusing me because I think the delegate pattern I'm using is common and I'm wondering how others have dealt with this issue.
The pattern:
I wrote a service, which executes an asynchronous request in a static function which takes a delegate instance. The delegate instance conforms to a protocol which requires implementation of a success and failure method. I've contrived an example which hits Google.com. Please ignore the Type safety issues in this example. The actual code I'm running to hit an endpoint and parse JSON is safer. I just wanted to come up with a very small snippet of code to depict the issue that's causing difficulty while testing:
protocol GoogleServiceDelegate {
func gotGoogle(str: String);
func gotError(str: String);
}
struct GoogleService {
static func getGoogle(delegate: GoogleServiceDelegate) {
let url: NSURL! = NSURL(string: "http://google.com")
NSURLSession.sharedSession().dataTaskWithURL(url) { data, response, error in
if let data = data {
let str: NSString! = NSString(data: data, encoding: NSUTF8StringEncoding)
delegate.gotGoogle(str as String)
} else {
delegate.gotError("\(error)")
}
}
}
}
Here's the test which illustrates the problem:
class AsyncTestingTests: XCTestCase {
func testExample() {
let responseExpectation = expectationWithDescription("Got google response!")
struct GoogleDelegate: GoogleServiceDelegate {
func gotGoogle(str: String) {
// expectations about response
responseExpectation.fulfill()
}
func gotError(str: String) {
// expectations about error
responseExpectation.fulfill()
}
}
let myGoogleServiceDelegate = GoogleDelegate()
GoogleService.getGoogle(myGoogleServiceDelegate)
waitForExpectationsWithTimeout(5) { _ in
print("Never got a response from Google :(")
}
}
}
The problem arises at the two .fulfill() lines. I get the following error from Xcode:
Struct declaration cannot close over value 'responseExpectation' defined in outer scope
I understand the error, but am unsure what to adjust... Is there a workaround for this which I can use in the test, or is there a better (easily testable) pattern for asynchronous callbacks than what I am attempting? If you know of a better testable solution, would you mind taking the time to write down an example?
Yes, you can not close over variables defined outside of struct, to workaround, we need to use closures/functions and pass it to the struct. Methods in struct can invoke it when they receive the response.
func testExample() {
let responseExpectation = expectationWithDescription("Got google response!")
//Let a function capture the fulfilling of the expectation
func fullFillExpectation(){
responseExpectation.fullFill()
}
struct GoogleDelegate: GoogleServiceDelegate {
var fullFiller : (()->Void)!
func gotGoogle(str: String) {
// expectations about response via invoke the closure
fullFiller()
}
func gotError(str: String) {
// expectations about error - invoke the closure
fullFiller()
}
}
//Create the delegate with full filler function.
let myGoogleServiceDelegate = GoogleDelegate(fullFiller: fullFillExpectation)
GoogleService.getGoogle(myGoogleServiceDelegate)
waitForExpectationsWithTimeout(5) { _ in
print("Never got a response from Google :(")
}
}
}
PS: I could not test this, please test and let me know.

Authenticated http request swift Alamofire

I'm struggling with getting this to work to make request to my API. Without a token works, but when I try to add additional headers, things turn to be complicated, for me.
First, the structure.
one class called: APIAsyncTask that makes the requests
one class called APIParams, just a data holder to send parameters to the APIAsyncTask class.
one class called DatabaseAPI that makes that builds the parameters, and send that to the APIAsyncTask class.
DatabaseAPI
func someMethod()
{
let task = APIAsyncTasks()
task.registerCallback { (error, result) -> Void in
print("Finished task, back at DatabaseAPI")
}
let params2 = APIParams(request: .GET, apiPath: "Posts/1", apiToken: "4iTX-56w")
task.APIrequest(params2)
}
APIAsyncTask
This part is for fixing another error, because manager was not global, the task got cancelled quickly.
var manager : Manager!
init(authenticatedRequest : Bool, token: String?)
{
manager = Alamofire.Manager()
print("Pre \(manager.session.configuration.HTTPAdditionalHeaders?.count)")
if(authenticatedRequest && token != nil)
{
var defaultHeaders = Alamofire.Manager.sharedInstance.session.configuration.HTTPAdditionalHeaders!
defaultHeaders["Authorization"] = "bearer \(token)"
let configuration = Manager.sharedInstance.session.configuration
configuration.HTTPAdditionalHeaders = defaultHeaders
manager = Alamofire.Manager(configuration: configuration)
}
print("Post \(manager.session.configuration.HTTPAdditionalHeaders?.count)")
}
After some decision making, it comes down to this part.
private func GetRequest(url: String!,token : String?, completionHandler: (JSON?, NSURLRequest?, NSHTTPURLResponse?, NSError?) -> () ) -> ()
{
print("Begin Get Request")
if(token != nil)//if token is not nil, make authenticated request
{
print("just before request: \(manager.session.configuration.HTTPAdditionalHeaders?.count)")
manager.request(.GET, url, parameters: nil, encoding: .JSON).responseJSON { (request, response, json, error) in
print("Get Request (authenticated), inside alamofire request")
var resultJson : JSON?
if(json != nil)
{
resultJson = JSON(json!)
}
completionHandler(resultJson, request, response, error)
}
}
else
{
//working part without token
So as the code is now, I get an error on completing:
Mattt himself gives the answer of using Alamofire.Manager.sharedInstance.session.configuration.HTTPAdditionalHeaders
, so that should be fine...
I suspect it has something to do with the multiple threads, according to this blog. Or, since it is something about CFNetwork, it could be because my API does not use SSL? I disabled NSAppTransportSecurity
I'm kind of new to swift, so examples would be really appreciated! Thankyou!
So the majority of your code looks solid.
The error leads me to believe that CFNetwork is having difficulty figuring out how to compute the protection space for the challenge. I would also assume you are getting a basic auth challenge since you are attaching an Authorization header.
Digging through your logic a bit more with this in mind led me to see that your not attaching your token to the string properly inside the Authorization header. You need to do the following instead.
defaultHeaders["Authorization"] = "bearer \(token!)"
Otherwise your Authorization header value is going to include Optional(value) instead of just value.
That's the only issue I can see at the moment. If you could give that a try and comment back that would be great. I'll update my answer accordingly if that doesn't actually solve your problem.
Best of luck!
You can add your headers in your request with Alamofire 2 and Swift 2.
For an example: go to example

iOS: pass data to another class

I have my class named "Service" where inside I do a lot of GET/POST request with Alamofire, an example of request id this
func requestDocuments(){
request(.POST, "http://example.com/json/docs")
.responseJSON { (_, _, JSON, error) in
if error == nil{
var response = JSON as NSArray
println("array document: \(response)")
//**** HERE I WANT PASS VALUE TO MY VIEW CONTROLLER
}
else{
}
}
}
and from my viewcontroller:
let service = Service.sharedInstance
service.requestDocuments()
What can I use? delegate method? or what?
what is the best solution in swift?
func requestDocuments(completion:(data:NSArray?)){
request(.POST, "http://example.com/json/docs")
.responseJSON { (_, _, JSON, error) in
if error == nil{
var response = JSON as NSArray
println("array document: \(response)")
//**** HERE I WANT PASS VALUE TO MY VIEW CONTROLLER
completion(data:response)
}
else{
completion(data:nil)
}
}
}
var reqDoc = requestDocuments(){ (data) -> Void in
if let _data = data {
dispatch_async(dispatch_get_main_queue()) {
//Do something with data
}
}
}
I think closures is the best solution.
Yes. There are 3 main ways to do this. The idea is you want to send off a call to a class, in this case, for networking, and have it come back sometime later and do something.
Delegates+Protocols are fantastic for this:
http://iosdevelopertips.com/objective-c/the-basics-of-protocols-and-delegates.html
As are Blocks
https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/WorkingwithBlocks/WorkingwithBlocks.html
The other popular way is KVO but that is not ideal for your example.
I will use closure in swift,
For example
class Service{
func requestDocuments(completion:(response:AnyObject)->()){
//After network is done
completion(response:data)
}
}
Then here to use
service.requestDocuments { (response) -> () in
//Here you can get response async
}
Use Delegate its the best approach.
Please see below example where delegation approach is demostrated
AFNetworking 2.0 - How to pass response to another class on success
Another approach would be NSNotification

Resources