如何依赖注入故事板托管的UIViewControllers?

Ort*_*ton 1 testing dependency-injection storyboard ios swift

大家好我正在尝试测试我项目的ViewControllers之一.这个类依赖于另一个辅助类,如下所示:

private let dispatcher: Dispatcher = Dispatcher.sharedInstance
private var loginSync = LoginSync.sharedInstance
private var metadataSync = MetadataSync.sharedInstance
Run Code Online (Sandbox Code Playgroud)

这些辅助类在UIViewController生命周期中使用,如viewDidLoad或viewWillAppear.在我的测试中,我使用UIStoryboard类实例化ViewController类,如下所示:

func testSearchBarAddedIntoNavigationViewForiOS11OrMore() {
    // Given a YourFlow ViewController embedded in a navigation controller
    let mockLoginSync = MockLoginSync()
    let storyboard = UIStoryboard(name: "Main", bundle: nil)

    // Here is too early and view controller is not instantiated yet and I can't assign the mock.
    let vc = storyboard.instantiateViewController(withIdentifier: "YourFlow")
    // Here is too late and viewDidLoad has already been called so assigning the mock at this point is pointless.
    let navigationController = UINavigationController(rootViewController: vc)

    // Assertion code
}
Run Code Online (Sandbox Code Playgroud)

所以我的问题是我需要能够模拟LoginSync类.在正常情况下,我会通过在类构造函数中将这些帮助程序作为参数传递来使用常规依赖项注入.在那种情况下,我无法做到这一点,因为我没有管理View Controller生命周期.因此,只要我实例化它,就已经使用了助手.

我的问题是:"有没有办法为我们无法控制的生命周期的View控制器进行依赖注入,或者至少是一种解决方法?

谢谢.

编辑:所以viewDidLoad被调用,因为我在didSet重写方法中使用IBOutlets,而不是因为调用了instantiateViewController.所以我可以移开该代码并在正确实例化视图控制器后进行注入.

few*_*ode 6

你可以UIVIewControllerCreation像这样包装:

class func createWith(injection: YourInjection) -> YourViewController {
    let storyboard = UIStoryboard(name: "Main", bundle: nil)
    let vc = storyboard.instantiateViewController(withIdentifier: "YourVCId") as? YourViewController
    vc.injected = injection
    return vc
}
Run Code Online (Sandbox Code Playgroud)

并使用它像:

let vc = YourViewController.createWith(<your injection>)
Run Code Online (Sandbox Code Playgroud)

这是一个例子:

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        let vc = RedViewController.createWith(injection: "some")
        navigationController?.pushViewController(vc, animated: true)
    }
}


class RedViewController: UIViewController {
    var injected: String = "" {
        didSet {
            print(#function)
        }
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .red
        print(#function)
    }

    class func createWith(injection: String) -> RedViewController {
        let storyboard = UIStoryboard(name: "Main", bundle: nil)
        let vc = storyboard.instantiateViewController(withIdentifier: "Red") as! RedViewController
        vc.injected = injection
        return vc
    }
}
Run Code Online (Sandbox Code Playgroud)

故事板设置:

在此输入图像描述

代码运行结果打印:

injected
viewDidLoad()
Run Code Online (Sandbox Code Playgroud)

如您所见,注入发生在viewDidLoad()之前