如何将 CoreData 暴露给 swift 包单元测试?
How to expose CoreData to swift package unit tests?
我正在尝试在我的 swift 包中测试 CoreData,因为 SPM 现在支持包括 .xcdatamodel 在内的捆绑资源,但我的测试似乎无法找到我的 NSManagedObjects。从测试中单元测试核心数据的步骤是什么?
当我尝试从测试创建 NSManagedObject 时出现此错误:
+entityForName: could not locate an entity named 'StriveUser' in this model. (NSInternalInconsistencyException)
我已经三重检查了命名,一切正确。
我正在通过测试创建这样的对象:
let object = NSEntityDescription.insertNewObject(forEntityName: "StriveUser", into: self.inMemoryStore.context)
这是我用于定位 .xcdatamodel 的代码:
fileprivate var managedObjectModel: NSManagedObjectModel = {
guard let managedObjectModel = NSManagedObjectModel.mergedModel(from: [Bundle.main]) else {
preconditionFailure("Error getting ManagedObjectModel")
}
return managedObjectModel
}()
final class InMemoryStore {
let context: NSManagedObjectContext
init() {
let description = NSPersistentStoreDescription()
description.type = NSInMemoryStoreType
description.shouldAddStoreAsynchronously = false
let container = NSPersistentContainer(name: Constants.modelName, managedObjectModel: managedObjectModel)
container.persistentStoreDescriptions = [description]
container.loadPersistentStores {_, error in
if let error = error {
fatalError("Failed to load store: \(error.localizedDescription)")
}
}
self.context = container.viewContext
}
}
如果您在包清单中声明 Swift 工具版本 5.3 或更高版本,您可以将资源与源代码捆绑为 Swift 包。例如,Swift 包可以包含资产目录、故事板等。
定义资源后,将为包创建一个新的静态 Bundle
引用。这可以使用 Bundle.module
.
访问
因此,对于您的 ManagedObjectModel
,您需要更新包参考。使用它的一个好方法是在你的包中有一个访问器,它将 return 模型。
有关其他信息,您可以查看 Apple 的开发人员文档 Bundling Resources with a Swift Package。
我 运行 遇到了类似的问题,每当我尝试对我的核心数据模型执行任何操作时,我的应用程序都会因“找不到 YourManagedObject”错误而崩溃。
当我将我的核心数据依赖项从 cocoapods 移动到 swift 包管理器时,这种情况就开始发生了。
但这是我的解决方案:
- 将
@objc(ManagedObjectName)
添加到我所有的 NSManagedObject 类
- 在核心数据模型编辑器的数据模型检查器中,删除
Current Project Module
并使用模块配置的默认值。
- 正如上面的答案所说,确保在加载 NSManagedObjectModel 时使用
Bundle.module
而不是 Bundle.main。
- 我重写了我们的核心数据堆栈以使用
NSPersistentStore
而不是根据 WWDC 2018 核心数据最佳实践手动设置所有内容(NSPersistentStoreCoordinator、NSManagedObjectContext 等...)
注意几点:
问题不是因为错误的包,我在迁移到 SPM 时已经应用了那个更改。由于某种原因,应用程序在运行时找不到我的任何 NSManagedObject 类。
在尝试#4 之前先尝试#1 和#2。我不知道 #4 是否有助于解决这个问题,因为当我删除 Current Project Module
时应用程序停止崩溃。不过,它确实清理了很多丑陋的遗留代码。
我构建了一个新的 bare-bones Swift 包来演示似乎是导致这些问题的错误,截至 Xcode 版本 13.3.1 (13E500a)
No NSEntityDescriptions in any model claim the NSManagedObject subclass 'TestModel.EntityMO' so +entity is confused. Have you loaded your NSManagedObjectModel yet ?
加载模型后立即中断,我可以看到实体存在:
(lldb) po model
(<NSManagedObjectModel: 0x6000015f8d70>) isEditable 1, entities {
Entity = "(<NSEntityDescription: 0x6000001c4b00>) name Entity, managedObjectClassName TestModel_TestModel.EntityMO,
<snip>
managedObjectClassName 似乎是问题所在。这是在模型中的 class 定义中使用 Current Product Module
的结果,它似乎将包 top-level 名称和源中包含的文件夹连接在一起。如果我将其替换为 TestModel
的 hard-coded 模块,则错误消失并且测试通过。不理想,但在我的情况下有效。
Xcode 对 swift 包中核心数据的支持似乎是 work-in-progress,因为编辑器仍然无法正确加载 .xcdatamodeld
文件。只是创建测试模型必须在另一个项目中完成并移动到包中,因为我无法将实体添加到空模型文件中。
作为参考,我还将包括我的模型初始化,这是非常基本的,但我相信相对于 Apple 指南而言是合理的。至少,它表明问题可能存在于 Bundle.module
使用之外。
public struct TestModel {
internal static let modelURL = Bundle.module.url(forResource: "Model", withExtension: "momd")!
public static func persistentContainer() -> NSPersistentContainer {
let model = NSManagedObjectModel(contentsOf: modelURL)!
let description = NSPersistentStoreDescription()
description.type = NSInMemoryStoreType
let container = NSPersistentContainer(name: "Test", managedObjectModel: model)
container.persistentStoreDescriptions = [description]
container.loadPersistentStores { storeDescription, error in
guard error == nil else {
fatalError("Could not load persistent stores. \(error!)")
}
}
return container
}
}
我正在尝试在我的 swift 包中测试 CoreData,因为 SPM 现在支持包括 .xcdatamodel 在内的捆绑资源,但我的测试似乎无法找到我的 NSManagedObjects。从测试中单元测试核心数据的步骤是什么?
当我尝试从测试创建 NSManagedObject 时出现此错误:
+entityForName: could not locate an entity named 'StriveUser' in this model. (NSInternalInconsistencyException)
我已经三重检查了命名,一切正确。
我正在通过测试创建这样的对象:
let object = NSEntityDescription.insertNewObject(forEntityName: "StriveUser", into: self.inMemoryStore.context)
这是我用于定位 .xcdatamodel 的代码:
fileprivate var managedObjectModel: NSManagedObjectModel = {
guard let managedObjectModel = NSManagedObjectModel.mergedModel(from: [Bundle.main]) else {
preconditionFailure("Error getting ManagedObjectModel")
}
return managedObjectModel
}()
final class InMemoryStore {
let context: NSManagedObjectContext
init() {
let description = NSPersistentStoreDescription()
description.type = NSInMemoryStoreType
description.shouldAddStoreAsynchronously = false
let container = NSPersistentContainer(name: Constants.modelName, managedObjectModel: managedObjectModel)
container.persistentStoreDescriptions = [description]
container.loadPersistentStores {_, error in
if let error = error {
fatalError("Failed to load store: \(error.localizedDescription)")
}
}
self.context = container.viewContext
}
}
如果您在包清单中声明 Swift 工具版本 5.3 或更高版本,您可以将资源与源代码捆绑为 Swift 包。例如,Swift 包可以包含资产目录、故事板等。
定义资源后,将为包创建一个新的静态 Bundle
引用。这可以使用 Bundle.module
.
因此,对于您的 ManagedObjectModel
,您需要更新包参考。使用它的一个好方法是在你的包中有一个访问器,它将 return 模型。
有关其他信息,您可以查看 Apple 的开发人员文档 Bundling Resources with a Swift Package。
我 运行 遇到了类似的问题,每当我尝试对我的核心数据模型执行任何操作时,我的应用程序都会因“找不到 YourManagedObject”错误而崩溃。
当我将我的核心数据依赖项从 cocoapods 移动到 swift 包管理器时,这种情况就开始发生了。
但这是我的解决方案:
- 将
@objc(ManagedObjectName)
添加到我所有的 NSManagedObject 类 - 在核心数据模型编辑器的数据模型检查器中,删除
Current Project Module
并使用模块配置的默认值。 - 正如上面的答案所说,确保在加载 NSManagedObjectModel 时使用
Bundle.module
而不是 Bundle.main。 - 我重写了我们的核心数据堆栈以使用
NSPersistentStore
而不是根据 WWDC 2018 核心数据最佳实践手动设置所有内容(NSPersistentStoreCoordinator、NSManagedObjectContext 等...)
注意几点:
问题不是因为错误的包,我在迁移到 SPM 时已经应用了那个更改。由于某种原因,应用程序在运行时找不到我的任何 NSManagedObject 类。
在尝试#4 之前先尝试#1 和#2。我不知道 #4 是否有助于解决这个问题,因为当我删除 Current Project Module
时应用程序停止崩溃。不过,它确实清理了很多丑陋的遗留代码。
我构建了一个新的 bare-bones Swift 包来演示似乎是导致这些问题的错误,截至 Xcode 版本 13.3.1 (13E500a)
No NSEntityDescriptions in any model claim the NSManagedObject subclass 'TestModel.EntityMO' so +entity is confused. Have you loaded your NSManagedObjectModel yet ?
加载模型后立即中断,我可以看到实体存在:
(lldb) po model
(<NSManagedObjectModel: 0x6000015f8d70>) isEditable 1, entities {
Entity = "(<NSEntityDescription: 0x6000001c4b00>) name Entity, managedObjectClassName TestModel_TestModel.EntityMO,
<snip>
managedObjectClassName 似乎是问题所在。这是在模型中的 class 定义中使用 Current Product Module
的结果,它似乎将包 top-level 名称和源中包含的文件夹连接在一起。如果我将其替换为 TestModel
的 hard-coded 模块,则错误消失并且测试通过。不理想,但在我的情况下有效。
Xcode 对 swift 包中核心数据的支持似乎是 work-in-progress,因为编辑器仍然无法正确加载 .xcdatamodeld
文件。只是创建测试模型必须在另一个项目中完成并移动到包中,因为我无法将实体添加到空模型文件中。
作为参考,我还将包括我的模型初始化,这是非常基本的,但我相信相对于 Apple 指南而言是合理的。至少,它表明问题可能存在于 Bundle.module
使用之外。
public struct TestModel {
internal static let modelURL = Bundle.module.url(forResource: "Model", withExtension: "momd")!
public static func persistentContainer() -> NSPersistentContainer {
let model = NSManagedObjectModel(contentsOf: modelURL)!
let description = NSPersistentStoreDescription()
description.type = NSInMemoryStoreType
let container = NSPersistentContainer(name: "Test", managedObjectModel: model)
container.persistentStoreDescriptions = [description]
container.loadPersistentStores { storeDescription, error in
guard error == nil else {
fatalError("Could not load persistent stores. \(error!)")
}
}
return container
}
}