Xcode单元测试中如何初始化WidgetKit的WidgetInfo结构体?
问题核心原因
WidgetKit 框架中的 WidgetInfo 是苹果未暴露公开构造方法的系统结构体,你之前尝试的方案走不通是必然的:
- 它没有提供对外可调用的公开初始化器,因此直接调用
WidgetInfo()会触发无可用初始化器的报错 - 它的所有存储属性(包括
family、kind等)都是系统内部管理的只读常量,外部没有写入权限;同时Swift要求针对导入的系统结构体编写扩展初始化器时,必须最终调用框架已经暴露的公开初始化器完成实例构造,外部无权直接给这类属性赋值,因此补空初始化器、自定义带参初始化器、直接给属性赋值的操作都会触发编译错误,靠扩展补初始化器的思路从原理上就不成立。
可行的单元测试方案
优先推荐协议抽象解耦的方案,无侵入、稳定性高,也是iOS单元测试处理系统类型依赖的标准做法:
- 定义属性对齐的抽象协议,把业务代码中实际用到的
WidgetInfo属性纳入协议定义
protocol WidgetInfoConvertible { var family: WidgetFamily { get } var kind: String { get } }
- 让系统原生
WidgetInfo默认遵循该协议,不需要写额外实现,因为原生类型本身就自带这些只读属性
extension WidgetInfo: WidgetInfoConvertible {}
- 修改原本接收
WidgetInfo参数的业务函数,将参数类型替换为WidgetInfoConvertible,生产环境传入真实的WidgetInfo实例时,原有逻辑完全不受影响 - 在单元测试target中自定义可控的Mock测试类型,遵循上述协议即可自由构造测试用例
struct MockWidgetInfo: WidgetInfoConvertible { let family: WidgetFamily let kind: String } // 测试代码中可按需构造任意场景的实例 let testSmallWidget = MockWidgetInfo(family: .systemSmall, kind: "com.test.demoWidget") let testMediumWidget = MockWidgetInfo(family: .systemMedium, kind: "com.test.demoWidget")
如果暂时不想修改业务函数的参数签名,不要尝试通过runtime强写内存、硬构造系统类型实例,这类做法在不同iOS版本上极易出现兼容性问题,测试稳定性极差。可以考虑在单元测试中挂载最小化的Widget Extension测试宿主,通过系统回调拿到真实的WidgetInfo实例传入测试,但这种方案配置成本远高于协议抽象方案,性价比极低。
内容的提问来源于stack exchange,提问作者colbrew
相关产品推荐
相关产品推荐

