台风加载故事板以编程方式显示执行异步实例化而不阻塞

时间:2016-03-22 22:16:59

标签: swift unit-testing storyboard xcode7 typhoon

我正在开发一个iOS应用程序,并试图将Typhoon集成到测试中。我目前正在尝试模拟来自故事板的视图控制器中的依赖项,因此在我的程序集中:

public dynamic var systemComponents: SystemComponents!
public dynamic func storyboard() -> AnyObject {
    return TyphoonDefinition.withClass(TyphoonStoryboard.self) {
        (definition) in
        definition.useInitializer("storyboardWithName:factory:bundle:") {
            (initializer) in
            initializer.injectParameterWith("Main")
            initializer.injectParameterWith(self)
            initializer.injectParameterWith(NSBundle.mainBundle())
        }
    }
}

我想创建一个CameraModeViewController(我是单元测试类),它依赖于系统摄像机功能提供的协议。依赖关系是dynamic var cameraProvider: CameraAPIProvider?。我想我正确地创建了一个替换协作程序集来替换systemComponents; MockSystemComponents是覆盖函数的SystemComponents的子类。这是我注入模拟的地方:

let assembly = ApplicationAssembly().activateWithCollaboratingAssemblies([
                            MockSystemComponents(camera: true)
                        ])
let storyboard = assembly.storyboard()
subject = storyboard.instantiateViewControllerWithIdentifier("Camera-Mode") as! CameraModeViewController

测试中的下一行代码是let _ = subject.view,我学到的是调用viewDidLoad并获取所有与故事板链接的IBOutlet的技巧,其中一个是此测试所必需的。< / p>

然而,我得到了非常神秘的结果:有时但并非总是如此,所有测试都失败了,因为在viewDidLoad我调用了依赖项(cameraProvider),我得到了一个&# 34;无法识别的消息发送到班级&#34;错误。该错误似乎表明在发送消息时(这是协议CameraAPIProvider中的正确实例方法),该字段当前是CLASS而不是实例:它将消息解释为+[MockSystemCamera cameraStreamLayer]为在错误消息中报告。

~~~ BUT ~~~

这里是踢球者:如果我在对assembly.storyboard()subject.view的调用之间添加断点,则测试总是通过。一切都设置正确,并且没有这个&#34;类方法&#34;正确地将消息发送到实例。伪造的解释。因此,我不得不怀疑Typhoon是否会在注射中进行某种异步程序,我必须等待?可能只有在处理故事板交付的视图控制器时?如果是这样,有没有办法确保它阻止?

在Typhoon的源代码中挖掘了一段时间后,我得到的印象是在TyphoonDefinition(Instance Builder) initializeInstanceWithArgs:factory:方法中有__block id instance暂时是Class类型,然后替换为该类型的实例;并且可能这可以异步调用而不会阻塞,因此注入的成员将保留为Class类型?

更新:添加MockSystemComponents(camera:)的代码。请注意,SystemComponents继承自TyphoonAssembly

@objc
public class MockSystemComponents: SystemComponents {
    var cameraAvailable: NSNumber

    init(camera: NSNumber) {
        self.cameraAvailable = camera
        super.init()
    }

    public override func systemCameraProvider() -> AnyObject {
        return TyphoonDefinition.withClass(MockSystemCamera.self) {
            (definition) in
            definition.useInitializer("initWithAvailable:") {
                (initializer) in
                initializer.injectParameterWith(self.cameraAvailable)
            }
        }
    }
}

UPDATE#2:我尝试使用属性注入替换MockSystemComponents.systemCameraProvider()中的构造函数注入。不同的问题,但我怀疑它的原因是相同的:现在,注入的属性(声明为可选)仍然是nil当我打开它时(但不总是 - 可能)大约4/5的测试运行失败,与之前大致相同。

更新#3 尝试使用以下代码块,根据this answer使用工厂构造(请注意,设置工厂直接不像OP那样工作,但是我认为我正确使用了为响应Jasper issue而添加的功能。结果与上面的Update#2使用属性注入时的结果相同,所以没有骰子。

1 个答案:

答案 0 :(得分:0)

这个问题实际上甚至在调用实例化之前就已经出现了。实际上,问题是程序集通常通常是有状态的。有几种方法可以解决这个问题,但我不建议使用我使用的方法 - 拥有成员变量和初始化方法。这样做的问题在于,在activateWithCollaboratingAssemblies方法中,枚举了程序集的所有实例方法以用于定义,并且实际上将在协作程序集上调用初始化程序。因此,即使您使用初始化程序创建程序集,也可能会使用虚假值再次调用它。

请注意,似乎存在异步行为的原因实际上是存在组合定义的非确定性顺序(将它们存储在NSDictionary中的属性)。这意味着如果activateWithCollaboratingAssemblies碰巧枚举首先依赖于状态的方法,它们就能正常工作;但是如果首先枚举初始化程序,并且状态被销毁,那么之后创建的定义将被识别。