Search code examples
swiftfunctiondispatchtestability

Combining testable code with static method dispatch in swift


I've been reading a lot about Swift's runtime lately, and became more and more interested in optimising my code using static method dispatch. This happens with the following methods:

  • struct methods
  • final class methods, i.e. declared with the final keyword, as private or in a final class
  • protocol methods that are defined in a protocol extension, without being declared in the protocol itself.

Problem is, non of these situations enables me to write testable code, at least not the way I do it now: injecting protocol entities that are replaced by mocks in unit testing.

So, is it possible to write testable code without giving up static method dispatch, and if so how does one go about it?

Thanks!


Solution

  • Generics is what you look for. You can abstract over a protocol, but the compiler still knows what exact type you are using, so there's no need for dynamic dispatch.

    protocol Dependency {
      func doSomething()
    }
    
    struct RealDependency: Dependency {
      func doSomething() {
        print("I'm doing real work")
      }
    }
    
    struct MockDependency: Dependency {
      func doSomething() {
        print("I'm the mock, so I do nothing")
      }
    }
    
    struct MyApp<D: Dependency> {
      let dependency: D
    
      func doSomething() {
        dependency.doSomething()
      }
    }
    
    let myAppReal = MyApp(dependency: RealDependency())
    let myAppMock = MyApp(dependency: MockDependency())
    
    myAppReal.doSomething() // Prints "I'm doing real work"
    myAppMock.doSomething() // Prints "I'm the mock, so I do nothing"
    

    However, note that in Swift, generics monomorphization is not guaranteed. So you might end with some form of dynamic dispatch anyway. See this link