多个NSEntityDescriptions声明NSManagedObject子类

问题描述 投票:18回答:6

我正在创建一个允许我使用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 nsmanagedobject nsmanagedobjectcontext
6个回答
8
投票

加载核心数据有点神奇,从磁盘加载模型并使用它意味着它注册某些类型。第二次加载尝试再次注册该类型,这显然告诉您已经注册了该类型的东西。

您只能加载一次Core Data并在每次测试后清理该实例。清理意味着删除每个对象实体然后保存。有一些功能可以为您提供所有可以获取和删除的实体。批量删除不可用于InMemory,尽管如此对象管理对象就在那里。

(可能更简单)替代方案是加载模型一次,将其存储在某处并在每个NSPersistentContainer调用上重用该模型,它具有使用给定模型的构造函数,而不是从磁盘再次加载它。


14
投票

在使用内存存储的单元测试环境中,最终会加载两个不同的模型:

  • 主Core Data堆栈在您的应用程序中加载的模型
  • 单元中加载的模型测试内存堆栈。

这会导致问题,因为显然+ [NSManagedObjectModel entity]会查看所有可用的模型,以便为NSManagedObject找到匹配的实体。由于它找到两个模型,它会抱怨。

解决方案是使用insertNewObjectForEntityForName:inManagedObjectContext:将对象插入上下文中。这将考虑上下文(以及因此,上下文的模型)来查找实体模型,并因此将其搜索限制为单个模型。

对我而言,它似乎是NSManagedObject init(managedObjectContext:)方法中的一个错误,似乎依赖于+[NSManagedObject entity]而不是依赖于上下文的模型。


5
投票

由于@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)
    }

}

我发现它here,所以完全的功劳应该去@shaps


2
投票

我在尝试使用以下目标进行CoreData相关单元测试时遇到了这个问题:

  • 用于速度的内存类型NSPersistentContainer堆栈
  • 为每个测试用例重新创建堆栈以擦除数据

作为Fabian的答案,这个问题的根本原因是managedObjectModel被多次加载。但是,可能有几个可能的managedObjectModel加载位置:

  1. 在App
  2. 在测试用例中,XCTestCase子类的每个setUp调用都试图重新创建NSPersistentContainer

所以解决这个问题要两倍。

  1. 不要在app中设置NSPersistentContainer堆栈。

您可以添加underTesting标志以确定是否设置它。

  1. 在所有单元测试中仅加载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
    }
}

以上应该足以解决在单元测试中发生此问题。


0
投票

我通过更改以下内容修复了警告:

  • 我在我的应用程序中加载了一个持久性存储,导致了这些警告。
  • 如果你在NSManagedObjectModel上做的事情,请确保你使用persistentStoreCoordinatorpersistentStoreContainer的模型。在我直接从文件系统加载它并得到警告之前。

我无法修复以下警告:

  • 之前我删除了我的整个持久存储,并在应用程序生命周期中创建了一个新容器。我无法找到如何修复此后发出的警告。

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)
© www.soinside.com 2019 - 2024. All rights reserved.