I think I found a nice way to stub NSURLSession using dependency injection.

Let's say you have an app that should fetch the info of a user from App.net and create a user object/struct from that info. To be able to unit test that feature you want to stub the HTTP request in the test.

You could use Mockingjay or you could write your own NSURLProtocol. But with Swift 1.2 there is an even simpler approach.

In the test you can inject the mock class to be used instead of NSURLSession.

Here is the view controller with the method to fetch the user data:

import UIKit

public class SwiftViewController: UIViewController {

  public var MySession = NSURLSession.self
  public var user: User?
  
  public func loadUser() {
    let url = NSURL(string: "https://api.app.net/users/@dasdom")
    let session = MySession.sharedSession()
    let task = session.dataTaskWithURL(url!, completionHandler: { (data, response, error) -> Void in
      let dataDict = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: nil) as! [String:AnyObject]
      
      let userDict = dataDict["data"] as! [String:AnyObject]
      
      self.user = User(username: userDict["username"] as! String, name: userDict["name"] as! String, id: userDict["id"] as! String)
      
    })
    task.resume()
  }
}

This code is straight forward. The only piece that is noteworthy it the first property:

public var MySession = NSURLSession.self

MySession is a NSURLSession type.

Let's write a mock for that class. You will have to mock NSURLSession and NSURLSessionDataTask. Here is the smallest implementation that can stub dataTaskWithURL(_:,completionHandler:):

class MockSession: NSURLSession {
  var completionHandler: ((NSData!, NSURLResponse!, NSError!) -> Void)?
  
  static var mockResponse: (data: NSData?, urlResponse: NSURLResponse?, error: NSError?) = (data: nil, urlResponse: nil, error: nil)
  
  override class func sharedSession() -> NSURLSession {
    return MockSession()
  }
  
  override func dataTaskWithURL(url: NSURL, completionHandler: ((NSData!, NSURLResponse!, NSError!) -> Void)?) -> NSURLSessionDataTask {
    self.completionHandler = completionHandler
    return MockTask(response: MockSession.mockResponse, completionHandler: completionHandler)
  }
  
  class MockTask: NSURLSessionDataTask {
    typealias Response = (data: NSData?, urlResponse: NSURLResponse?, error: NSError?)
    var mockResponse: Response
    let completionHandler: ((NSData!, NSURLResponse!, NSError!) -> Void)?
    
    init(response: Response, completionHandler: ((NSData!, NSURLResponse!, NSError!) -> Void)?) {
      self.mockResponse = response
      self.completionHandler = completionHandler
    }
    override func resume() {
      completionHandler!(mockResponse.data, mockResponse.urlResponse, mockResponse.error)
    }
  }
}

The mock session class has a static property (which will be used as a class property here) to store the mock response and a property to keep a reference of the passed in completion handler when dataTaskWithURL... is called. It overrides the class method sharedSession() and dataTaskWithURL(_:,completionHandler).

The mock task class is defined within the mock session class. It has two properties, dataTaskWithURL(_:,completionHandler) and completionHandler and it overrides resume() to call the completion handler with the test response instead.

The test then looks like:

func testExample() {
  
  let jsonData = NSJSONSerialization.dataWithJSONObject(["meta": ["code": 200], "data": ["username": "dasdom", "name": "Dpminik Hauser", "id": "1472"]], options: nil,
error: nil)
  let urlResponse = NSHTTPURLResponse(URL: NSURL(string: "https://api.app.net/posts/stream/global")!, statusCode: 200, HTTPVersion: nil, headerFields: nil)
  
  MockSession.mockResponse = (jsonData, urlResponse: urlResponse, error: nil)
  viewController.MySession = MockSession.self
  
  XCTAssertTrue(viewController.user == nil, "")
  
  viewController.loadUser()
  
  XCTAssertEqual(viewController.user!.username, "dasdom", "")
}

Note the dependency injection on the line:

viewController.MySession = MockSession.self

That's it. Light and easy to read. HTTP stubbing done within minutes.

I would love to hear your feedback to this stubbing method.