我正在创建一个允许使用Core Data的框架。在框架的测试目标中,我配置了一个名为MockModel.xcdatamodeld
的数据模型。它包含一个名为MockManaged
的实体,该实体具有单个Date
属性。
为了创建逻辑测试,我正在创建一个内存存储。当我想验证我的保存逻辑时,我创建了一个内存中的实例并使用它。但是,我一直在控制台中获得以下输出:
2018-08-14 20:35:45.340157-0400 xctest[7529:822360] [error] warning: Multiple NSEntityDescriptions claim the NSManagedObject subclass 'LocalPersistenceTests.MockManaged' so +entity is unable to disambiguate.
CoreData: warning: Multiple NSEntityDescriptions claim the NSManagedObject subclass 'LocalPersistenceTests.MockManaged' so +entity is unable to disambiguate.
2018-08-14 20:35:45.340558-0400 xctest[7529:822360] [error] warning: 'MockManaged' (0x7f986861cae0) from NSManagedObjectModel (0x7f9868604090) claims 'LocalPersistenceTests.MockManaged'.
CoreData: warning: 'MockManaged' (0x7f986861cae0) from NSManagedObjectModel (0x7f9868604090) claims 'LocalPersistenceTests.MockManaged'.
2018-08-14 20:35:45.340667-0400 xctest[7529:822360] [error] warning: 'MockManaged' (0x7f986acc4d10) from NSManagedObjectModel (0x7f9868418ee0) claims 'LocalPersistenceTests.MockManaged'.
CoreData: warning: 'MockManaged' (0x7f986acc4d10) from NSManagedObjectModel (0x7f9868418ee0) claims 'LocalPersistenceTests.MockManaged'.
2018-08-14 20:35:45.342938-0400 xctest[7529:822360] [error] error: +[LocalPersistenceTests.MockManaged entity] Failed to find a unique match for an NSEntityDescription to a managed object subclass
CoreData: error: +[LocalPersistenceTests.MockManaged entity] Failed to find a unique match for an NSEntityDescription to a managed object subclass
以下是我用于创建内存存储的对象:
class MockNSManagedObjectContextCreator {
// MARK: - NSManagedObjectContext Creation
static func inMemoryContext() -> NSManagedObjectContext {
guard let model = NSManagedObjectModel.mergedModel(from: [Bundle(for: self)]) else { fatalError("Could not create model") }
let coordinator = NSPersistentStoreCoordinator(managedObjectModel: model)
do {
try coordinator.addPersistentStore(ofType: NSInMemoryStoreType, configurationName: nil, at: nil, options: nil)
} catch {
fatalError("Could not create in-memory store")
}
let context = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
context.persistentStoreCoordinator = coordinator
return context
}
}
下面是构成我的MockManaged
实体的原因:
class MockManaged: NSManagedObject, Managed {
// MARK: - Properties
@NSManaged var date: Date
}
下面是构成我的XCTestCase
的原因:
class Tests_NSManagedObjectContext: XCTestCase {
// MARK: - Object Insertion
func test_NSManagedObjectContext_InsertsManagedObject_WhenObjectConformsToManagedProtocol() {
let context = MockNSManagedObjectContextCreator.inMemoryContext()
let changeExpectation = expectation(forNotification: .NSManagedObjectContextObjectsDidChange, object: context, handler: nil)
let object: MockManaged = context.insertObject()
object.date = Date()
wait(for: [changeExpectation], timeout: 2)
}
// MARK: - Saving
func test_NSManagedObjectContext_Saves_WhenChangesHaveBeenMade() {
let context = MockNSManagedObjectContextCreator.inMemoryContext()
let saveExpectation = expectation(forNotification: .NSManagedObjectContextDidSave, object: context, handler: nil)
let object: MockManaged = context.insertObject()
object.date = Date()
do {
try context.saveIfHasChanges()
} catch {
XCTFail("Expected successful save")
}
wait(for: [saveExpectation], timeout: 2)
}
func test_NSManagedObjectContext_DoesNotSave_WhenNoChangesHaveBeenMade() {
let context = MockNSManagedObjectContextCreator.inMemoryContext()
let saveExpectation = expectation(forNotification: .NSManagedObjectContextDidSave, object: context, handler: nil)
saveExpectation.isInverted = true
do {
try context.saveIfHasChanges()
} catch {
XCTFail("Unexpected error: \(error)")
}
wait(for: [saveExpectation], timeout: 2)
}
}
我在做什么导致测试中的错误?
答案 0 :(得分:13)
在带有内存存储的单元测试的上下文中,最终会加载两个不同的模型:
这会导致问题,因为显然<TargetLatestRuntimePatch>
会查看所有可用的模型来为您的NSManagedObject查找匹配的实体。由于它找到两个模型,因此会抱怨。
解决方案是使用+ [NSManagedObjectModel entity]
在上下文中插入对象。这将考虑上下文(以及上下文的模型)以查找实体模型,从而将其搜索限制为单个模型。
对我来说,这似乎是insertNewObjectForEntityForName:inManagedObjectContext:
方法中的一个错误,该方法似乎依赖于NSManagedObject init(managedObjectContext:)
而不是依赖于上下文的模型。
答案 1 :(得分:7)
加载核心数据有点神奇,从磁盘加载模型并使用它意味着它注册了某些类型。第二次加载会尝试再次注册该类型,这显然告诉您已经为该类型注册了某些东西。
您只能加载一次核心数据,并在每次测试后清理该实例。清理意味着删除每个对象实体,然后保存。有一些功能可以为您提供所有实体,然后您可以提取和删除它们。批量删除在InMemory中不可用,但是它是按对象管理的对象。
(可能更简单)的替代方法是一次加载模型,将其存储在某个位置,然后在每次NSPersistentContainer
调用中重用该模型,它具有构造函数,可以使用给定的模型而不是从磁盘再次加载。 / p>
答案 2 :(得分:4)
@Kamchatka指出由于使用NSManagedObject init(managedObjectContext:)
而显示警告。使用NSManagedObject initWithEntity:(NSEntityDescription *)entity insertIntoManagedObjectContext:(NSManagedObjectContext *)context
可以消除此警告。
如果您不想在测试中使用更高版本的构造函数,则只需在测试目标中创建NSManagedObject
扩展名即可override
的默认行为:
import CoreData
public extension NSManagedObject {
convenience init(usedContext: NSManagedObjectContext) {
let name = String(describing: type(of: self))
let entity = NSEntityDescription.entity(forEntityName: name, in: usedContext)!
self.init(entity: entity, insertInto: usedContext)
}
}
答案 3 :(得分:3)
我通过将ManagedObjectModel作为CoreData管理器类的类属性来解决此问题:
class PersistenceManager {
let storeName: String!
static var managedObjectModel: NSManagedObjectModel = {
let managedObjectModel = NSManagedObjectModel.mergedModel(from: [Bundle(for: PersistenceManager.self)])!
return managedObjectModel
}()
...
}
...然后,在我的测试中,当我设置PersistentContainer时,我直接引用了该模型:
lazy var inMemoryContainer: NSPersistentContainer = {
// Reference the model inside the app, rather than loading it again, to prevent duplicate errors
let container = NSPersistentContainer(name: "TestContainer", managedObjectModel: PersistenceManager.managedObjectModel)
let description = NSPersistentStoreDescription()
description.type = NSInMemoryStoreType
description.shouldAddStoreAsynchronously = false
container.persistentStoreDescriptions = [description]
container.loadPersistentStores { (description, error) in
precondition(description.type == NSInMemoryStoreType)
if let error = error {
fatalError("Create an in-memory coordinator failed \(error)")
}
}
return container
}()
这还有一个好处,就是不需要将妈妈类或实体类直接添加到测试包中,我发现我以前需要这样做。
答案 4 :(得分:3)
[error] 警告:多个 NSEntityDescriptions 声明 ...
此警告是由声明为同一托管对象子类的多个托管对象模型引起的。
在核心数据单元测试的上下文中,这没什么大不了的,因为我们知道它会破坏任何东西。但是,通过添加静态托管对象模型并将其用于我们创建的每个持久容器,也很容易消除警告消息。下面代码片段中的 gatsby-plugin-typography
是 Core Data 模型文件的文件名。
以下代码片段基于 Xcode 生成的 Core Data 模板代码
xcdatamodeld
答案 5 :(得分:2)
我尝试进行具有以下目的的CoreData相关单元测试时遇到了这个问题:
作为Fabian的答案,此问题的根本原因是managedObjectModel
被多次加载。但是,托管对象模型的加载可能有几个可能的地方:
setUp
调用都试图重新创建NSPersistentContainer 因此,解决此问题有两个方面。
您可以添加underTesting
标志来确定是否进行设置。
managedObjectModel
我为managedObjectModel
使用了一个静态变量,并使用它来重新创建内存中的NSPersistentContainer。
一些摘录如下:
class UnitTestBase {
static let managedObjectModel: NSManagedObjectModel = {
let managedObjectModel = NSManagedObjectModel.mergedModel(from: [Bundle(for: UnitTestBase.self)])!
return managedObjectModel
}()
override func setUp() {
// setup in-memory NSPersistentContainer
let storeURL = NSPersistentContainer.defaultDirectoryURL().appendingPathComponent("store")
let description = NSPersistentStoreDescription(url: storeURL)
description.shouldMigrateStoreAutomatically = true
description.shouldInferMappingModelAutomatically = true
description.shouldAddStoreAsynchronously = false
description.type = NSInMemoryStoreType
let persistentContainer = NSPersistentContainer(name: "DataModel", managedObjectModel: UnitTestBase.managedObjectModel)
persistentContainer.persistentStoreDescriptions = [description]
persistentContainer.loadPersistentStores { _, error in
if let error = error {
fatalError("Fail to create CoreData Stack \(error.localizedDescription)")
} else {
DDLogInfo("CoreData Stack set up with in-memory store type")
}
}
inMemoryPersistentContainer = persistentContainer
}
}
以上内容足以解决单元测试中出现的问题。
答案 6 :(得分:1)
只需使用singleton一次创建您的managedContext,然后再使用它即可。它帮助我解决了同样的问题。
class CoreDataStack {
static let shared = CoreDataStack()
private init() {}
var managedContext: NSManagedObjectContext {
return self.storeContainer.viewContext
}
//...
}
答案 7 :(得分:1)
我在 BatchInsert 内存单元测试中得到了这个。我改用构造函数作为实体名称而不是实体实际实体,这消除了警告。
我用过这个:
NSBatchInsertRequest(entityName: entityNameAlert(), objects: ...) //<- entityNameAlert() is a method that returns my entity name as a string
代替:
NSBatchInsertRequest(entity: Alert.entity(), objects: ...)
此外,我在内存存储中的 batchDelete 上获得了它,我能够通过使用上面提供的扩展创建对象来消除它:
答案 8 :(得分:0)
我通过更改以下内容来修正警告:
NSManagedObjectModel
上进行操作,请确保您使用的是persistentStoreCoordinator
或persistentStoreContainer
中的模型。在我直接从文件系统加载它并得到警告之前。我无法修复以下警告:
答案 9 :(得分:0)
当对象模型有多个实例时,CoreData会抱怨。我发现最好的解决方案是在一个静态定义它们的地方。
struct ManagedObjectModels {
static let main: NSManagedObjectModel = {
return buildModel(named: "main")
}()
static let cache: NSManagedObjectModel = {
return buildModel(named: "cache")
}()
private static func buildModel(named: String) -> NSManagedObjectModel {
let url = Bundle.main.url(forResource: named, withExtension: "momd")!
let managedObjectModel = NSManagedObjectModel.init(contentsOf: url)
return managedObjectModel!
}
}
然后确保在实例化容器时明确地传递这些模型。
let container = NSPersistentContainer(name: "cache", managedObjectModel: ManagedObjectModels.cache)
答案 10 :(得分:0)
我已经两次访问了persistentContainer,我删除了一次,它修复了警告并可以正常工作。