我正在创建一个允许我使用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)
}
}
我在做什么导致我的测试中的错误?
加载核心数据有点神奇,从磁盘加载模型并使用它意味着它注册某些类型。第二次加载尝试再次注册该类型,这显然告诉您已经注册了该类型的东西。
您只能加载一次Core Data并在每次测试后清理该实例。清理意味着删除每个对象实体然后保存。有一些功能可以为您提供所有可以获取和删除的实体。批量删除不可用于InMemory,尽管如此对象管理对象就在那里。
(可能更简单)替代方案是加载模型一次,将其存储在某处并在每个NSPersistentContainer
调用上重用该模型,它具有使用给定模型的构造函数,而不是从磁盘再次加载它。
在使用内存存储的单元测试环境中,最终会加载两个不同的模型:
这会导致问题,因为显然+ [NSManagedObjectModel entity]
会查看所有可用的模型,以便为NSManagedObject找到匹配的实体。由于它找到两个模型,它会抱怨。
解决方案是使用insertNewObjectForEntityForName:inManagedObjectContext:
将对象插入上下文中。这将考虑上下文(以及因此,上下文的模型)来查找实体模型,并因此将其搜索限制为单个模型。
对我而言,它似乎是NSManagedObject init(managedObjectContext:)
方法中的一个错误,似乎依赖于+[NSManagedObject entity]
而不是依赖于上下文的模型。
由于@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)
}
}
我在尝试使用以下目标进行CoreData相关单元测试时遇到了这个问题:
作为Fabian的答案,这个问题的根本原因是managedObjectModel
被多次加载。但是,可能有几个可能的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
}
}
以上应该足以解决在单元测试中发生此问题。
我通过更改以下内容修复了警告:
NSManagedObjectModel
上做的事情,请确保你使用persistentStoreCoordinator
或persistentStoreContainer
的模型。在我直接从文件系统加载它并得到警告之前。我无法修复以下警告:
当存在多个对象模型实例时,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)