【问题标题】:Mocking a static class method in a swift unit test in a swifty way?以快速的方式在快速单元测试中模拟静态类方法?
【发布时间】:2017-06-29 11:15:27
【问题描述】:

我是一位经验丰富的 Objective-c 程序员,但我不能对 Swift 说同样的话,在不使用 OCMock 之类的框架的情况下,我很难在 swift 中对一个类进行单元测试。

问题:我正在将 Firebase 集成到一个混合的 Objective-C/Swift 项目中,我需要根据应用的构建配置对其进行配置。

我为此编写了一个 Swift 类(将由 obj-c 应用程序委托使用),但是由于 firebase 框架是通过静态类方法配置的,确切地说是FIRApp.configure(with: FIROptions),我需要模拟这个方法以某种方式对其进行单元测试。

我的代码,没有任何依赖注入句柄,看起来像这样:

@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
    }

}

我已经进行了一些研究,但到目前为止我没有找到任何适合我的解决方案,但是我正在考虑以下之一:

  • 我可以传递一个默认为FIRApp.configure(with:) 的函数,但是我应该从objective-c 执行此操作,并且该函数还接受一个参数,我在语法上遇到了困难
  • 我可以在 FIRApp 周围使用包装器,但我想避免它,除非唯一可行的清洁解决方案。
  • 我可以继续使用协议并进行依赖倒置,但是作为静态方法,我又在语法上苦苦挣扎,我找不到使用带有静态方法的模拟类进行 DI 的简单方法。李>

作为参考(个人和可能需要它的人),这些是我发现有用的一些资源,我将继续挖掘:

同时,我们将非常感谢您的每一次帮助。

作为旁注,有很多方法可以解决这个问题,而不用苦于模拟静态类方法,但我的目的是找到一种模拟它的方法,以便更好地理解最佳实践测试更复杂的情况。

【问题讨论】:

    标签: swift unit-testing dependency-injection xctest swift-protocols


    【解决方案1】:

    你确实可以做任何这些。

    闭包参数

    您可以让您的 configureFirebase 函数采用默认为您最初使用的“应用程序”闭包:

    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)
    }
    

    协议

    您需要一个Configurable 协议,然后在默认情况下使FIRApp 符合它:

    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)
      }
    }
    

    如果你只是打算在一种方法中使用它,那么它只是暂时的状态,它可能应该是一个函数参数而不是存储属性。

    (如果不清楚它是持久状态还是瞬态状态,因为类的全部意义在于调用单个函数,也许您甚至不需要类,只需要一个函数。)

    【讨论】:

    • 最后我不需要再做这个了,但无论如何感谢你的回答;)。只是一个棘手的问题,applier闭包有一个默认值,这是否与objective-c桥接?
    • 我怀疑它并没有像写的那样桥接,但如果你不得不这样做的话,你可以以一种可桥接的方式完成同样的事情。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-01-30
    • 1970-01-01
    相关资源
    最近更新 更多