我正在开发一个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使用属性注入时的结果相同,所以没有骰子。
答案 0 :(得分:0)
这个问题实际上甚至在调用实例化之前就已经出现了。实际上,问题是程序集通常通常是有状态的。有几种方法可以解决这个问题,但我不建议使用我使用的方法 - 拥有成员变量和初始化方法。这样做的问题在于,在activateWithCollaboratingAssemblies
方法中,枚举了程序集的所有实例方法以用于定义,并且实际上将在协作程序集上调用初始化程序。因此,即使您使用初始化程序创建程序集,也可能会使用虚假值再次调用它。
请注意,似乎存在异步行为的原因实际上是存在组合定义的非确定性顺序(将它们存储在NSDictionary中的属性)。这意味着如果activateWithCollaboratingAssemblies
碰巧枚举首先依赖于状态的方法,它们就能正常工作;但是如果首先枚举初始化程序,并且状态被销毁,那么之后创建的定义将被识别。