6

I have been following this tutorial to stub out URLSession. The example was done by creating a protocol and extending the existing URLSession.

protocol URLSessionProtocol {
    typealias DataTaskResult = (Data?, URLResponse?, Error?) -> Void
    func dataTask(with request: NSURLRequest, completionHandler: @escaping DataTaskResult) -> URLSessionDataTaskProtocol
}

extension URLSession: URLSessionProtocol {
    func dataTask(with request: NSURLRequest, completionHandler: @escaping DataTaskResult) -> URLSessionDataTaskProtocol {
        return dataTask(with: request, completionHandler: completionHandler) as URLSessionDataTaskProtocol
    }
}

The unit tests work as expected. But when I try to run the real thing, the URLSession -> datatask() gets into an infinite loop and it crashes. It seems to be that datatask() is calling itself.

What am I overlooking, please?

UPDATE:

protocol URLSessionDataTaskProtocol {
    var originalRequest: URLRequest? { get }
    func resume()
}

extension URLSessionDataTask: URLSessionDataTaskProtocol {}
Houman
  • 64,245
  • 87
  • 278
  • 460
  • Did you define a MockURLSession class for testing? – thorb65 Mar 27 '18 at 09:46
  • Yes I did. Tests are running fine. But when I want to use it in production by injecting `URLSession.shared` I get this endless loop. – Houman Mar 27 '18 at 10:34
  • and in production you return `URLSessionDataTask` instead of the protocol in the extension? – thorb65 Mar 27 '18 at 10:46
  • I'm not sure if I follow what you mean. I have updated the question with `URLSessionDataTaskProtocol` implementation. This protocol is implemented for `MockUrlSession`. But the real `UrlSession` should be just using the URLSessionDataTask out of the box, correct? I don't think I have missed any dependency injection. – Houman Mar 27 '18 at 12:16
  • yes, that is what i ment :-) – thorb65 Mar 27 '18 at 13:14
  • See the real article here: http://masilotti.com/testing-nsurlsession-input/ – Mike Taverne Mar 28 '18 at 02:03
  • One of the commenters notes that in Swift 4 it was infinite looping, but he made a fix. – Mike Taverne Mar 28 '18 at 02:09
  • @MikeTaverne Thanks Mike. I tried it but I still get the loop. It's quite strange, because he says that's a fix for Swift 4, but he is using `func dataTaskWithURL(with url)`, which seems like Swift 2.0 syntax, where it should be `dataTask(with: URL)`. Unless I am totally mistaken, his `dataTaskWithURL` is now a new custom method and isn't overriding anything in `URLSession` anymore. What do you think? – Houman Mar 28 '18 at 06:30

2 Answers2

3

I have finally found the solution. It’s fascinating as we missed the wood for the trees. There are two issues:

1) It seems that Swift 4 has changed the signature for dataTask(with: NSURLRequest) to dataTask(with: URLRequest)

Therefore the line in my opening question would only match to the Protocol's func signature, and it would never hit the dataTask inside URLSession, hence the infinite loop. To solve this issue I had to change NSURLRequest to URLRequest and refactor the code accordingly.

2) The signature remains vague, hence it is better to store the result as dataTask first with a cast to URLSessionDataTask and then return the variable.

New refactored Code for Swift 4:

typealias DataTaskResult = (Data?, URLResponse?, Error?) -> Void

protocol URLSessionProtocol {
    func dataTask(with request: URLRequest, completionHandler: @escaping DataTaskResult) -> URLSessionDataTaskProtocol
}

extension URLSession: URLSessionProtocol {
    func dataTask(with request: URLRequest, completionHandler: @escaping DataTaskResult) -> URLSessionDataTaskProtocol {
        let task:URLSessionDataTask = dataTask(with: request, completionHandler: {
            (data:Data?, response:URLResponse?, error:Error?) in completionHandler(data,response,error) }) as URLSessionDataTask
        return task
    }
}

I also found I had to inject URLSession.shared as a singleton and not as URLSession(), otherwise it could crash.

Houman
  • 64,245
  • 87
  • 278
  • 460
3

Came here to understand how to mock URLSession tasks such as URLSessionDataTask?

Circa Swift 5, it is much easier to mock the URLProtocol that URLSession is using to send the request.

See Unit Testing URLSession using URLProtocol.

Donal Lafferty
  • 5,807
  • 7
  • 43
  • 60