在Swift中模拟单例/sharedInstance [英] Mocking singleton/sharedInstance in Swift
问题描述
我有一个要使用XCTest进行测试的类,该类看起来像这样:
I have a class that I want to test using XCTest, and this class looks something like this:
public class MyClass: NSObject {
func method() {
// Do something...
// ...
SingletonClass.sharedInstance.callMethod()
}
}
该类使用如下实现的单例:
The class uses a singleton that is implemented as this:
public class SingletonClass: NSObject {
// Only accessible using singleton
static let sharedInstance = SingletonClass()
private override init() {
super.init()
}
func callMethod() {
// Do something crazy that shouldn't run under tests
}
}
现在进行测试.我想测试method()
确实执行了应该执行的操作,但是我不想调用callMethod()
中的代码(因为它执行了一些可怕的异步/网络/线程操作,这些操作不应在测试下运行)的MyClass,并且会使测试崩溃).
Now for the test. I want to test that method()
actually does what it is supposed to do, but I don't want to invoke the code in callMethod()
(because it does some horrible async/network/thread stuff that shouldn't run under tests of MyClass, and will make the tests crash).
所以我基本上想做的是:
So what I basically would like to do is this:
SingletonClass = MockSingletonClass: SingletonClass {
override func callMethod() {
// Do nothing
}
let myObject = MyClass()
myObject.method()
// Check if tests passed
这显然不是有效的Swift,但是您明白了.如何仅针对此特定测试覆盖callMethod()
,以使其无害?
This obviously isn't valid Swift, but you get the idea. How can I override callMethod()
just for this particular test, to make it harmless?
编辑:我尝试使用一种形式的依赖注入来解决此问题,但遇到了大问题.我创建了一个自定义的初始化方法,该方法仅用于测试,因此可以创建如下对象:
I tried solving this using a form of dependency injection, but ran into big problems. I created a custom init-method just to be used for tests such that I could create my objects like this:
let myObject = MyClass(singleton: MockSingletonClass)
让MyClass看起来像这样
and let MyClass look like this
public class MyClass: NSObject {
let singleton: SingletonClass
init(mockSingleton: SingletonClass){
self.singleton = mockSingleton
}
init() {
singleton = SingletonClass.sharedInstance
}
func method() {
// Do something...
// ...
singleton.callMethod()
}
}
在测试代码中与其余代码混合是一件令人不愉快的事情,但是还可以. BIG问题是在我的项目中,我有两个这样构造的单例,两者互相引用:
Mixing in test code with the rest of the code is something I find a bit unpleasing, but okay. The BIG problem was that I had two singletons constructed like this in my project, both referencing each other:
public class FirstSingletonClass: NSObject {
// Only accessible using singleton
static let sharedInstance = FirstSingletonClass()
let secondSingleton: SecondSingletonClass
init(mockSingleton: SecondSingletonClass){
self.secondSingleton = mockSingleton
}
private override init() {
secondSingleton = SecondSingletonClass.sharedInstance
super.init()
}
func someMethod(){
// Use secondSingleton
}
}
public class SecondSingletonClass: NSObject {
// Only accessible using singleton
static let sharedInstance = SecondSingletonClass()
let firstSingleton: FirstSingletonClass
init(mockSingleton: FirstSingletonClass){
self.firstSingleton = mockSingleton
}
private override init() {
firstSingleton = FirstSingletonClass.sharedInstance
super.init()
}
func someOtherMethod(){
// Use firstSingleton
}
}
第一次使用一个单例时,这会造成死锁,因为init方法将等待另一个单例的init方法,依此类推...
This created a deadlock when one of the singletons where first used, as the init method would wait for the init method of the other, and so on...
推荐答案
我最终使用代码
class SingletonClass: NSObject {
#if TEST
// Only used for tests
static var sharedInstance: SingletonClass!
// Public init
override init() {
super.init()
}
#else
// Only accessible using singleton
static let sharedInstance = SingletonClass()
private override init() {
super.init()
}
#endif
func callMethod() {
// Do something crazy that shouldn't run under tests
}
}
这样,我可以在测试过程中轻松模拟我的课程:
This way I can easily mock my class during tests:
private class MockSingleton : SingletonClass {
override callMethod() {}
}
在测试中:
SingletonClass.sharedInstance = MockSingleton()
通过将-D TEST
添加到应用测试目标的构建设置"中的其他Swift标志"中,激活仅测试代码.
The test-only code is activated by adding -D TEST
to "Other Swift Flags" in Build Settings for the app test target.
这篇关于在Swift中模拟单例/sharedInstance的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!