Recently I watched a video by Jon Reid. Jon Reid explains how he uses unit tests to test networking code. This video was an eye opener for me. If you haven't watched it yet, do it now.

Here is how his approach looks like in Swift using NSURLSession:

First we need a property to inject the mock url session in the test:

public class Foo {

  public var session: URLSession = NSURLSession.sharedSession()

}

Note the type of session . It is of type URLSession. URLSession is a protocol which declares one method:

public protocol URLSession {
  func dataTaskWithURL(url: NSURL, completionHandler: ((NSData!, NSURLResponse!, NSError!) -> Void)?) -> NSURLSessionDataTask
}

To make the compiler happy with the above definition we need to tell it that NSURLSession implements the URLSession protocol:

extension NSURLSession: URLSession {
}

The networking code in this example is quite easy. It just fetches a user profile from App.net:

let urlString = "https://api.app.net/users/@(username)"
let task = session.dataTaskWithURL(NSURL(string: urlString)!, completionHandler: { (data, response, error) -> Void in
      
  let rawDictionary = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: nil) as! [String:AnyObject]
  self.user = self.userFromDictionay(rawDictionary)
      
})
task.resume()

The method userFromDictionay(_:) extracts and unwraps the elements which are needed to create a user and returns a user or nil if not all elements could be extracted:

func userFromDictionay(dictionary: [String:AnyObject]) -> User? {
  if let rawUser = dictionary["data"] as? [String:AnyObject],
    username = rawUser["username"] as? String,
    name = rawUser["name"] as? String,
    counts = rawUser["counts"] as? [String:Int],
    followers = counts["followers"],
    following = counts["following"],
    posts = counts["posts"] {
        
      return User(username: username, name: name, numberOfPosts: posts, followers: followers, following: following)
  }
  return nil
}

To store the user, we also need a user property:

  public var user: User?

The user looks like this:

import Foundation

public struct User {
  public let username: String
  public let name: String
  public let numberOfPosts: Int
  public let followers: Int
  public let following: Int
  
  public init(username: String, name: String, numberOfPosts: Int, followers: Int, following: Int) {
    self.username = username
    self.name = name
    self.numberOfPosts = numberOfPosts
    self.followers = followers
    self.following = following
  }
}

The initializer is needed because we need to initialize a user in the tests and it seems that the generated initializer is declared as internal.

That's all for the networking code.

Let's switch to the test code. The mock url session looks like this:

class MockURLSession: URLSession {
    
  typealias Handler = (NSData!, NSURLResponse!, NSError!) -> Void
  var completionHandler: Handler?
  var url: NSURL?
    
  func dataTaskWithURL(url: NSURL, completionHandler: ((NSData!, NSURLResponse!, NSError!) -> Void)?) -> NSURLSessionDataTask {
    self.url = url
    self.completionHandler = completionHandler
    return NSURLSessionDataTask()
  }
}

Note that the mock url session stores the completion handler in a property.

The test looks like this:

func testFetchingOfUserSetUserProperty() {
  // Arrange
  let mockURLSession = MockURLSession()
  secondViewController.session = mockURLSession
  
  // Act
  secondViewController.fetchUserWithUsername("dasdom")
  let userDictionary = ["data": ["username": "dasdom", "name": "Dominik", "counts": ["followers": 11, "following": 22, "posts": 33]]] as NSDictionary
  let data = NSJSONSerialization.dataWithJSONObject(userDictionary, options: nil, error: nil)
  mockURLSession.completionHandler!(data!, nil, nil)
  
  // Assert
  let testUser = User(username: "dasdom", name: "Dominik", numberOfPosts: 33, followers: 11, following: 22)
  XCTAssertTrue(secondViewController.user! == testUser, "should be equal")
}

First we inject the mock url session. Then we fetch a user and call the caught completionHandler with a test JSON. Finally we assert that the fetched user is as we expected.

To make this work we also need to override the == operator:

public func ==(left: User, right: User) -> Bool {
  if left.username != right.username {
    return false
  }
  if left.name != right.name {
    return false
  }
  if left.numberOfPosts != right.numberOfPosts {
    return false
  }
  if left.followers != right.followers {
    return false
  }
  if left.following != right.following {
    return false
  }
  return true
}

Keep in mind that the response of a NSURLDataTask is delivered on a background thread. So you can't use this approach to check if a label got updated as a result of the response. But in this case you would test two things at the same time. You shouldn't do this anyway.

If you have any comments or questions about this, ping be on App.net or Twitter.