快速地在快速单元测试中模拟静态类方法? [英] Mocking a static class method in a swift unit test in a swifty way?
问题描述
我是一位经验丰富的Objective-c程序员,但我不能对Swift这么说,因为我很难在不使用OCMock之类的框架的情况下,快速地对类进行测试。
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.
问题:我正在将Firebase集成到一个混合的Objective-C / Swift项目中,我需要根据构建对其进行配置
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.
我为此编写了一个Swift类(将由obj-c应用程序委托使用),但是由于配置了Firebase框架通过静态类方法,恰好是 FIRApp.configure(with:FIROptions)
,我需要以某种方式模拟此方法以便对其进行单元测试。
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:
- 我可以传递一个默认为
FIRApp.configure(with:)
的函数,但是我应该从Objective-c和该函数中执行此操作还接受一个参数,我在语法上苦苦挣扎 - 我可以在FIRApp周围使用包装器,但是除非有唯一可行的干净解决方案,否则我想避免使用它。
- 我可以继续使用协议并进行依赖关系反转,但是由于方法是静态的,我再次在语法上苦苦挣扎,我找不到一种简单的方法来通过带有静态方法的模拟类来进行DI。 / li>
- 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.
推荐答案
您确实可以执行任何操作
You can indeed do any of those.
您可以使用 configureFirebase
函数取一个 appl ier闭包,默认为您最初使用的闭包:
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)
}
协议
您需要可配置
协议,然后符合 FIRApp
的默认情况:
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.)
这篇关于快速地在快速单元测试中模拟静态类方法?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!