在Swift中模拟单例/sharedInstance [英] Mocking singleton/sharedInstance in Swift

查看:253
本文介绍了在Swift中模拟单例/sharedInstance的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个要使用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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆