Mocking Dependencies with Generics

TL;DR: You can inject code dependencies in a transparent way by using generics and typealias.

Writing code in Swift is fun. Sometimes so much fun that we forget about an important detail.

Writing Tests. Yes. Sometimes we forget to write tests. We’ve all been there. Honestly.

What to do? First of all - you open the existing empty test file and begin to implement a test. But soon you will notice it’s not that easy to test your code independently. Many parts are intertwined. You realize - you better get some kind of dependency injection.

There’s more than one way to skin a cat 🐱

Here is a quote from wikipedia:

In software engineering, dependency injection is a software design pattern that implements inversion of control for resolving dependencies. A dependency is an object that can be used (a service). An injection is the passing of a dependency to a dependent object (a client) that would use it. The service is made part of the client’s state.[1] Passing the service to the client, rather than allowing a client to build or find the service, is the fundamental requirement of the pattern.

Basically what we want to do is - while testing one part of the code base - we want to fake (mock) the rest of the codebase.

Other ecosystems have some well-established techniques for this, like e.g. Spring on the JVM. Spring provides different flavors (like e.g. XML or Annotations) to glue your code together. Plus it provides an easy way to change that configuration for a test.

Turning to Swift:

Swift is not yet 2 years old. We are still searching for best practices. Every single application is different, thus the way I propose Dependency Injection may not work for every kind of application. Let’s start at the beginning.

You should google “Dependency Injection Swift” now to read about some common patterns.

First there was an item 📦

We will build a really simple use case and add some more complexity later on. Say hello to:

/// the item
struct Item {
  let date: NSDate
}
/// a model that takes items 
struct ModelFixed {
  let items: [Item]
  // return only valid items with a date later than now
  var validItems: [Item] {
    let now = NSDate() 
    return items.filter { $0.date.laterDate(now) == $0.date  }
  }
}

The two structs look pretty straight forward. In real life, however, this is really hard to test.

Why?
Because we use: let now = NSDate().
Every time we call NSDate() it returns the NSDate of now.
Let’s imagine the following situation:

  • we have a list of items, which are valid when the item’s date is after now
  • we want to ensure that only valid items are returned from the model
  • furthermore we want to check the valid items 10 minutes later
  • and maybe it is important for us to check the same model a day later

Freeze the world ❄️

Our first approach is to use a global func for getting now.

// a global var to change the 'now'
var timeShift: NSDate? = nil
func getNow() -> NSDate {
  // either we shift or we use `da real now`
  return timeShift ?? NSDate()
}
 
struct ModelGlobal {
  let items: [Item]
  var validItems: [Item] {
    // we always get the NSDate we deserve
    let now = getNow() 
    return items.filter { $0.date.laterDate(now) == $0.date  }
  }
}

// --------------------------------------------------------------------------
// MARK: - Test 
// --------------------------------------------------------------------------

let items = (1...10)
  .map { NSDate().dateByAddingTimeInterval(Double($0) * 10) }
  .map(Item.init(date:))
let globalModel = ModelGlobal(items: items)

// check valid: 'now'
assert(globalModel.validItems.count == 10)
// jump to tomorrow
timeShift = NSDate().dateByAddingTimeInterval(60 * 60 * 24)
//  check valid: 'tomorrow'
assert(globalModel.validItems.count == 0)

Pro: super easy to implement

Con: feels clunky + if we forget to reset the timeShift … 👹

Come on, we will never ever forget to reset the timeShift because we wrap it like:

// closure executed at a different `now` than `NSDate()`
func withShiftedTime(c: () -> ()) {
  timeShift = NSDate().dateByAddingTimeInterval(60 * 60 * 24)
  c()
  timeShift = nil
}
// only inside the closure the time is different
withShiftedTime {
  assert(globalModel.validItems.count == 0)
}

The world outside ☎️

Btw. time to add complexity: Did you know that valid items have to pass through a web API before they can be returned? Me neither.

Seems like we will have to implement a WebService and pass the valid items to the server for final verification. The WebServicewill be called in the model. But wait - when writing tests for the model we need to be able to mock the service.

Easy, therefore we initialize the model withWebService. Have a look:

// --------------------------------------------------------------------------
// MARK: - 1st Dependency: WebService 
//
// We start with a protocol and add a real and a mock webservice
// --------------------------------------------------------------------------

protocol WebServiceType {
  /// should be promise-based ... you have to imagine 
  func checkValid(items: [Item], completion: (Bool) -> ())
}
/// real web wervice
struct WebService: WebServiceType {
  func checkValid(items: [Item], completion: (Bool) -> ()) {
    // do stuff - not interesting for the moment ... 
  }
}
/// mocked web service 
struct WebServiceMock: WebServiceType {
  func checkValid(items: [Item], completion: (Bool) -> ()) {
    completion(true) // fake a valid call
  }
}

// --------------------------------------------------------------------------
// MARK: - 2nd Dependency:  DateProvider
//
// we also use a protocol and provide a Now and a Tomorrow
// --------------------------------------------------------------------------

protocol DateProvider {
  var now: NSDate { get }
}
/// always the real current NSDate
struct Now: DateProvider {
  var now: NSDate {
    return NSDate()
  }
}
/// always the real current NSDate plus 1 day
struct Tomorrow: DateProvider {
  var now: NSDate {
    return NSDate().dateByAddingTimeInterval(60 * 60 * 24)
  }
}
// --------------------------------------------------------------------------
// MARK: - Model with injected dependencies
//
// in fact: there is way more code for configuring the dependencies
// than the business code itself
// --------------------------------------------------------------------------

struct ModelInjected {
  let items: [Item]
  let dateProvider: DateProvider
  let webService: WebServiceType
  /// provide all dependencies at init
  init(items: [Item], dateProvider: DateProvider = Now(), webService: WebServiceType){
    self.items = items
    self.dateProvider = dateProvider
    self.webService = webService
  }
 //
  var validItems: [Item] {
    // how do you test that?
    let now = dateProvider.now
    return items.filter { $0.date.laterDate(now) == $0.date  }
  }
 // 
  func sync(then callback: ([Item]) -> ()) {
    webService.checkValid(validItems){ ok in
      callback(ok ? self.validItems : [])
    }
  }
}

// --------------------------------------------------------------------------
// MARK: - Test
// --------------------------------------------------------------------------

///inject now + mock web service
let modelInjected = ModelInjected(items: items, dateProvider: Now(), webService: WebServiceMock())

assert(modelInjected.validItems.count == 10)
modelInjected.sync { assert($0.count == 10) }

/// inject tomorrow + mock web service
let modelInjectedTomorrow = ModelInjected(items: items, dateProvider: Tomorrow(), webService: WebServiceMock())

assert(modelInjectedTomorrow.validItems.count == 0)
modelInjectedTomorrow.sync { assert($0.count == 0) }

Kinda works.

Pro: super flexible and decoupled

Con: feels clunky

And to be honest: There is too much code in the model only related to dependency handling. Too much noise. The more dependencies you get, the more code you write for managing them inside your model. The ModelGlobalhad 8 lines of code. (w/o web service - fair enough), while ModelInjectednearly tripled that. I am not sure if I want to do that.

Plus think about:

let _ = ModelInjected(
  items: items, 
  dateProvider: Now(), 
  webService: WebService()
)

let _ = ModelInjected(
  items: items, 
  dateProvider: Tomorrow(), 
  webService: WebServiceMock()
)

Those dependencies are loosely coupled, but in my opinion they belong together in a semantic way.
I even go one step further and claim:

The dependencies are the world your model lives in.

There is a Real World, a Mocked World and maybe many many more.

Parallel Worlds 🌓

When our model is able to exist in parallel worlds, it might be a good idea to define it as generic type. But what kind of boundary shall we use for the generic type?

/// the outer world
/// knows about the `now`, the services and there is even logging on top!
protocol Environment {
  static var now: NSDate { get }
  static var webService: WebServiceType { get }
  static var loggingService: LoggingServiceType { get }
}

Next up - we create the Real World:

// ----------------------------------------------------------------------------------------------
// MARK: - RealWorldEnvironment™
//
// very fine granular configs possible, we start with:
// - RealWorldEnvironment protocol
// - RealWorldEnvironment protocol extension
// - RealWorld struct as a nominal type for the generic
// ----------------------------------------------------------------------------------------------

protocol RealWorldEnvironment: Environment {}

extension RealWorldEnvironment {
  static var now: NSDate {
    return NSDate()
  }
  static var webService: WebServiceType {
    return WebService()
  }
}

struct RealWorld: RealWorldEnvironment {
  static var loggingService: LoggingServiceType {
    return LoggingService<RealWorld>()
  }
}

Only one step is missing to see the end result:

An environment-aware model that:

  • returns validItems with date after nowof environment
  • has a sync func that provides validItems to the web service of the environment, logs the server result (logging service of environment) and calls the callback with the corresponding items
// ----------------------------------------------------------------------------------------------
// MARK: - ModelFor 
//
// An Environment aware Type
// ----------------------------------------------------------------------------------------------

typealias Model = ModelFor<RealWorld>

struct ModelFor<Env: Environment> {
  let items: [Item]

  var validItems: [Item] {
    let now = Env.now
    return items.filter { $0.date.laterDate(now) == $0.date  }
  }

  func sync(then callback: ([Item]) -> ()) {
    Env.webService.checkValid(validItems){ ok in
      Env.loggingService.log("Returned \(ok)")
      callback(ok ? self.validItems : [])
    }
  }
}

We end up with:

  • a generic ModelFor taking a type conforming to Environment
  • a typealias Modelrepresenting ModelFor<RealWorld>
  • no code necessary to manage the dependencies with properties or whatever

All you write is: let modelNow = Model(items: items)

What about mocking?

// ----------------------------------------------------------------------------------------------
// MARK: - MockedWorldEnvironment
//
// the possible parallel worlds
// ----------------------------------------------------------------------------------------------

/// sunny -> Online
protocol MockedOnlineWorldEnvironment: Environment {}

/// stormy -> Offline
protocol MockedOfflineWorldEnvironment: Environment {}

/// the online base world
extension MockedOnlineWorldEnvironment {

  static var webService: WebServiceType {
    return WebServiceMock()
  }
}

/// the offline base world
extension MockedOfflineWorldEnvironment {

  static var webService: WebServiceType {
    return WebServiceOfflineMock()
  }
}

// ----------------------------------------------------------------------------------------------
// MARK: - Parallel Worlds
// ----------------------------------------------------------------------------------------------

struct WorldInTenSeconds: MockedOnlineWorldEnvironment {

  static var now: NSDate {
    return NSDate().dateByAddingTimeInterval(10)
  }

  static var loggingService: LoggingServiceType {
    return LoggingService<WorldInTenSeconds>()
  }
}

struct WorldTomorrow: MockedOnlineWorldEnvironment {

  static var now: NSDate {
    return NSDate().dateByAddingTimeInterval(60 * 60 * 24)
  }

  static var loggingService: LoggingServiceType {
    return LoggingService<WorldTomorrow>()
  }
}

struct WorldYesterdayOffline: MockedOfflineWorldEnvironment {

  static var now: NSDate {
    return NSDate().dateByAddingTimeInterval(-60 * 60 * 24)
  }

  static var loggingService: LoggingServiceType {
    return LoggingService<WorldYesterdayOffline>()
  }
}

We define a couple of different worlds and name them:

  • WorldInTenSeconds
  • WorldTomorrow
  • WorldYesterdayOffline

The clear naming immediately gives an idea what kind of dependencies are used. And suddenly writing tests and changing conditions is a no-brainer.

// lets define 2 more typealiases in the test
typealias ModeInTenSeconds = ModelFor<WorldInTenSeconds>
typealias ModelYesterdayOffline = ModelFor<WorldYesterdayOffline>

/// a function to check an environment-aware model
/// - validCount
/// - and after sync
func check<E>(model: ModelFor<E>, hasValidItems validCount: Int, afterSync: Int? = nil){
  //
  print("->", E.self, "has", model.validItems.count)
  // make sure the count is correct
  assert(model.validItems.count == validCount)
  // then check if after sync the count is correct as well
  model.sync(then: {
    let afterSyncCount = afterSync ?? validCount
    //
    print("after accessing server", afterSyncCount)
    assert($0.count == afterSyncCount)
  })
}

///  now
let modelNow = Model(items: items)
check(modelNow, hasValidItems: 10)

///  in ten seconds
let modeInTenSeconds = ModeInTenSeconds(items: items)
check(modeInTenSeconds, hasValidItems: 9)

 /// yesterday
let modelYesterday = ModelYesterday(items: items)
check(modelYesterday, hasValidItems: 10)

/// tomorrow (no typealias ... )
let modelTomorrow =  ModelFor<WorldTomorrow>(items: items)
check(modelTomorrow, hasValidItems: 0)

/// tomorrow but offline
let modelYesterdayOffline = ModelYesterdayOffline(items: items)
check(modelYesterdayOffline, hasValidItems: 10, afterSync: 0)

What does the output look like:

-> RealWorld has 10
-> WorldInTenSeconds has 9
2016-05-21 11:30:10 +0000 Returned true
after accessing server 9
-> WorldYesterday has 10
2016-05-20 11:30:00 +0000 Returned true
after accessing server 10
-> WorldTomorrow has 0
2016-05-22 11:30:00 +0000 Returned true
after accessing server 0
-> WorldYesterdayOffline has 10
2016-05-20 11:30:00 +0000 Returned false
after accessing server 0

Even the time output of the logging service is changing according to the Environment.

  • no need to manage global state
  • no need to explicitly inject dependencies
  • no need to use the generic type in the code: just use the typealias

Bonus Feature

Refactoring looks like:

struct MyComplexThing {}
typealias MyComplexThing = ComplexThingFor<RealWorld>
struct ComplexThingFor<Env: Environment> {}

In my opinion the generic approach is a lean Swift solution for a well known problem. Of course you can argue that it is a little bit verbose when you already use a generic model. But looking ahead: Swift 3 will give us generic typealias - so no need to worry.

// Swift 3
struct MyGenericModelFor<E: Environment, T> {}
typealias MyGenericModel<T> = MyGenericModelFor<RealWorld, T>

Interested in playing around? Here we go with a full playground:

The gist of it 👻

👻 Feel free to hit me on twitter. @elmkretzer