Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mocking a static class method in a swift unit test in a swifty way?

I'm a seasoned Objective-c programmer but I can't say the same for Swift, I'm having a hard time unit testing a class in swift without using frameworks like OCMock.

The Problem: I'm integrating Firebase into a mixed Objective-C/Swift project, and I need to configure it based on the build configuration of the app.

I've written a Swift class for that (that will be used by the obj-c app delegate), however since the firebase framework is configured trough a static class method, precisely FIRApp.configure(with: FIROptions), I need to mock this method somehow in order to unit test it.

My code, without any handle for Dependency Injection, looks like that:

@objc class FirebaseConfigurator: NSObject{

    func configureFirebase(){

        let config = configManager.buildConfiguration

        var optionsPlistBaseName = getPlistName()

        let optionsFile = Bundle.main.path(forResource: optionsPlistBaseName, ofType: "plist")

        guard let opts = FIROptions(contentsOfFile: optionsFile) else{
            assert(false, "fatal: unable to load \(optionsFile)")
            return
        }

        FIRApp.configure(with: opts)

    }

    func getPlistName() -> String{
        // retrieves correct plist name and returns it
    }

}

I've done some research but so far I didn't find nothing that fits my solution, however I was thinking of one of the following:

  • I could pass a function that defaults to FIRApp.configure(with:) however I should do this from objective-c and the function also accepts a parameter, I was struggling with the syntax
  • I could use a wrapper around FIRApp, but I wanted to avoid it unless the only viable clean solution.
  • I could keep on playing with protocols and do dependency inversion, however being the method static I was struggling with the syntax again, I can't find an easy way to do DI with a mock class with a static method.

As a reference (both personal and for who might need it) these are some of the resources I found useful and upon which I will keep on digging:

  • Dealing with static cling in Swift
  • This Question
  • This article about generic unit testing

In the meanwhile, every help would be really appreciated.

As a sidenote, there are many ways I can solve this problem without struggling with mocking a static class method, but my aim here is to find out a way of mocking it in order to have a better understanding of the best practices when testing more complex situations.

like image 674
dev_mush Avatar asked Feb 11 '17 00:02

dev_mush


1 Answers

You can indeed do any of those.

Closure Argument

You can have your configureFirebase function take an "applier" closure that defaults to what you originally used:

func configureFirebase(
    using apply: (_ options: FIROptions) -> Void
        = { opts in FIRApp.configure(opts) }
) {
  // building |opts| as before
  // Now replace this: FIRApp.configure(with: opts)
  apply(opts)
}

Protocols

You need a Configurable protocol, and then to conform FIRApp to it for the default case:

protocol Configurable {
  static func configure(with options: FIROptions)
}

extension FIRApp: Configurable {}

class FirebaseConfigurator {
  var configurable: Configurable
  init(configurable: Configurable = FIRApp) {
    self.configurable = configurable
  }

  func configureFirebase() {
    //load |opts|…
    configurable.configure(with: opts)
  }
}

If you're just going to use this in one method, though, it's merely transient state, and it should probably be a function argument rather than stored property.

(If it's unclear whether it's persistent or transient state because the whole point of the class is to call a single function, perhaps you don't even need a class, just a function.)

like image 61
Jeremy W. Sherman Avatar answered Oct 13 '22 10:10

Jeremy W. Sherman