velaru blog

Mocking AlamoFire Responses

Kris Steigerwald | [ 2017-02-15 ]

In recent weeks I have been looking for a simple approach to mocking responses in Alamofire. Being relatively new to Swift and IOS development, I wanted to keep it simple. Avoid adding a library or new dependency wasn't crucial yet preferred.

After a long look across the interwebs, nothing turned up in a manner as I'd hoped, so I decided on using one of the popular mock/stub frameworks. Setting up the tests against our models wasn't painful, and I was mostly satisfied with the coverage they provided. So I moved on to another story.

The next story in line dealt with providing guest access to the application. Some ideas were kicked around with the team. Since we didn't wish to create a reserved guest account, it only made sense to try and intercept the network request altogether. It would be better if we could move the guest access into an offline context.

I could try and use the mock library for this purpose. But that just felt like a reach. After having my head deep into mocking, I began to noodle on a clean way to mock the responses from Alamofire.

URLProtocol

I did a bit more digging and drummed up an article from a Yahoo engineer from a while back. Here he points out you can override the startLoading method on the URLProtocol to intercept the request and return the response of your choosing. I gave it a shot but didn't have immediate results.

From there I began to dig a little more into AlamoFires tests. There I found a test nestled away which appeared to be doing something similar to our friend's article from earlier. I brought the updated URLProtocol implementation in, and after some setup, I was able to log responses from the startLoading method.



 override func startLoading() {
        let data:NSData = mocks.find(request) as! NSData
        let client = self.client
        let response = HTTPURLResponse(url: (request.url)!, statusCode: 200, httpVersion: "HTTP/1.1", headerFields: cannedHeaders)
        
        client?.urlProtocol(self, didReceive: response!, cacheStoragePolicy: URLCache.StoragePolicy.notAllowed)
        cannedResponse = data
        client?.urlProtocol(self, didLoad: cannedResponse as! Data)
        client?.urlProtocolDidFinishLoading(self)
        
        activeTask?.resume()
    }

Response Handling: Aka Enums, Enums, Enums

Now that I have access to the override, All I'd need to do was to match up against the incoming request and return a fixture. Which takes us to the first line of the startLoading method.

My first instinct was to handle this in a functional side effect free way. Wrap the response in and array, then map, filter, reduce my way to victory. After wrangling up some code, I began to wonder if using an enum might simplify the steps.

I was attempting to catch the last two fragments of a REST endpoint then determine to which action it belonged. If the HTTP method was a GET, will it be an index or show action? While a simple regex matcher might be the best choice, I wanted to know more about enums.

Ah, init the enum with the HTTP method then allow the type settings to point to the handler. Indeed this seemed nice, easy to read and allowed me to flex a bit of enum muscle.



enum MockDirection {
    case GET, PUT, POST
    
    init(str: String) {
        switch str {
        case "GET":
            self = .GET
        case "PUT":
            self = .PUT
        case "POST":
            self = .POST
        default:
            self = .GET
        }
    }
    
    func isNotToken(_ item: String) -> Bool {
        let num = Int(item)
        return num == nil
    }
    
    func kind(_ tokens: Array = []) ->  [String] {
        
        if(isNotToken(tokens.last!) && self == .GET) {
            //Is a index actionk
            return [tokens.last!, self.output.last!]
        }
        
        if(!isNotToken(tokens.last!) && self == .GET) {
            //Is a show action
            return [tokens.first!, self.output.first!]
        }
        
        if(isNotToken(tokens.last!) && self == .POST) {
            //Is a create action
            return [tokens.last!, self.output.first!]
        }
        
        return [tokens.first!, output.last!]
    }
    
    var output:Array {
        switch self {
        case .GET: return ["SHOW", "INDEX"]
        case .PUT: return ["UPDATE"]
        case .POST: return ["CREATE"]
        }
    }
    
}

To Be Continued: Click here to see code example