marinbenc

Making burritos with Swift

Unless you’re the type of person who likes to write everything themselves, chances are you’ll have a lot of dependencies in your code on classes from external frameworks. These don’t often fit within your personal (or company’s) API guidelines or architecture. You also can’t apply dependency inversion as easily as with your own classes.

Since in Swift you can extend any type (including types from other frameworks) with whichever methods or computed variables you please, controlling external types is easy.

The approach I use for this is wrapping the external classes into a protocol I control. It boils down to extending the external class to conform to one of your protocols.

One thing that’s important to keep in mind is that the extension should have as little logic as possible, since you probably won’t be testing your mocks.

In a way, wrapping classes in protocols is like making a burrito. The protocol extension is the tortilla, and the external class are all the delicious ingredients in the tortilla. You want the tortilla to be as thin as possible, while still providing a manageable structure that you can work with.

Mocking NSUserDefaults with “forced” protocol conformance

A realistic depiction of forcing NSUserDefaults to conform to your protocol

The principle of dependency inversion is fairly simple — make your dependencies instances of protocols (or interfaces in other languages), not concrete implementations. This makes your code more modular, more testable and easier to refactor.

The trouble is, how do you wrangle an external class into conforming to a protocol. Well, there are two approaches to this: the submissive and the dominant approach.

Let’s take a look at those two approaches with a concrete example: NSUserDefaults.

NSUserDefaults’ main API are the objectForKey(:)](https://developer.apple.com/library/ios/documentation/Cocoa/Reference/Foundation/Classes/NSUserDefaults_Class/#//apple_ref/occ/instm/NSUserDefaults/objectForKey:) and [setObject(:forKey:) methods. This is what we will make mockable.

The submissive approach is to make your protocol the same as the external class’s API.

struct MyUserDefaults {
 func setObject(value: AnyObject?, forKey key: String)
 func objectForKey(defaultName: String)-> AnyObject?
}

These methods have the exact same signature as the methods in NSUserDefaults. Then, it is simply a matter of extending NSUserDefaults to conform to our protocol.

extension NSUserDefaults: MyUserDefaults {}

And now we can have an instance of MyUserDefaults as our dependency, instead of NSUserDefaults. *Which means *we can easily mock the user’s preferences.

struct UserService {
  
  let defaults: MyUserDefaults
 
  init(defaults: MyUserDefaults) {
    self.defaults = defaults
  }
 
  var displayName: String? {
    return defaults.objectForKey(“displayName”) as? String
  }

}

Our UserService can take any MyUserDefaults instance, so we can mock whatever data we want in our unit tests, to see if it performs as we intend.

However sometimes you don’t want the look and feel of external methods’ signatures polluting your code.

The dominant approach is where you make the external class conform to your own API. You know best how your code should look like. There’s no reason to compromise because of some outdated (or just plain badly designed) API.

The main principle is the same — declare a protocol, and make the external class conform to that protocol.

Let’s say we want to access the user’s preferences via a subscript, just like a dictionary. Adding custom subscripts is easy to do in Swift. We’ll just create a protocol with a subscript.

protocol BetterUserDefaults: class {
  subscript(key: String)-> AnyObject? { get set }
}

Then we’ll extend NSUserDefaults to conform to our new protocol.

extension NSUserDefaults: BetterUserDefaults {
  subscript(key: String)-> AnyObject? {
    get {
      return objectForKey(key)
    } set {
      setObject(newValue, forKey: key)
    }
  }
}

Now we have made NSUserDefaults conform to our own API standards, and we can write better code because of that.

class BetterUserService {
 
  private let defaults: BetterUserDefaults
 
  init(defaults: BetterUserDefaults) {
    self.defaults = defaults
  }
 
  var displayName: String? {
    get {
      return defaults[“displayName”] as? String
    } set {
      defaults[“dispayName”] = newValue
    }
  }
}

Mocking Alamofire & the trouble with external types

This is all well and good with NSUserDefaults, which works with types from Foundation or the Swift Standard Library. But sometimes external methods take external types as parameters.

*Alamofire *is a widely used networking library, and networking is one of those things we mock a lot. Mocking your networking layer provides several advantages. You no longer wait for requests to finish (which means your tests run faster). You also have total control over whether the request should fail or not, so you are able to test both the happy and not-so-happy code-paths.

One of Alamofire’s main methods is the request method:

Alamofire.request(method: Method, URLString: URLStringConvertible, parameters: [String : AnyObject]?, encoding: ParameterEncoding, headers: [String : String]?)

You’ll notice only two of the five parameters are a standard type. The other three (Method, URLStringConvertible and ParameterEncoding) are all types defined within the Alamofire framework. This means that in order to call this method, we have to import Alamofire into our file, which renders our mocks useless.

So far, I have not come up with a good solution for this. The best I could think of was to declare my own types, and convert them to Alamofire’s in the protocol extension.

enum MyMethod {
  case GET
  case POST
  //…
}
enum MyParameterEncoding {
  case URL
  case JSON
}
protocol Networking {
 
  func request(method: MyMethod, url: String, params: [String: AnyObject]?, parameterEncoding: MyParameterEncoding, headers: [String: String]?)
 
}

Here we have created copies of Alamofire’s Method and ParameterEncoding types. Then we declared a new protocol which has a similar method to Alamofire, but one that takes our own types instead of Alamofire’s.

Now we’ll make Alamofire conform to our Networking protocol.

extension Alamofire.Manager: Networking {
 
  func request(method: MyMethod, url: String, params: [String: AnyObject]?, parameterEncoding: MyParameterEncoding, headers: [String: String]?) {
 
    let alamofireParameterEncoding: ParameterEncoding = {
      switch parameterEncoding {
        case .URL: return .URL
        case .JSON: return .JSON
      }
    }()
 
    let alamofireMethod: Method = {
      switch method {
        case .GET: return .GET
        case .POST: return .POST
        //…
      }
    }()
 
    self.request(alamofireMethod, url, parameters: params, encoding: alamofireParameterEncoding, headers: headers)
  }
 
}

As you can see, this is pretty tedious to write, but it’s also fairly mechanical. Perhaps with the introduction of better reflection in Swift 4, things like this could be automated.

If you have a better solution for this, please don’t hesitate to write a response. And if you liked the article, press the Recommend button below!