NSPersistentContainer和UnitTests与iOS10

Nyc*_*cen 5 core-data ios swift ios10

我的单元测试核心数据设置存在问题.

我在AppDelegate中使用默认/新的Core Data堆栈设置

class AppDelegate: UIResponder, UIApplicationDelegate {
    lazy var persistentContainer: NSPersistentContainer = {
        let container = NSPersistentContainer(name: "GenericFirstFinder")
        container.loadPersistentStores(completionHandler: { (storeDescription, error) in
            if let error = error as NSError? {
                fatalError("Unresolved error \(error), \(error.userInfo)")
            }
        })
        return container
    }()
}
Run Code Online (Sandbox Code Playgroud)

为了测试,我有一个自定义函数来创建托管上下文

func setupInMemoryManagedObjectContext() -> NSManagedObjectContext {
    let container = NSPersistentContainer(name: "GenericFirstFinder")

    let description = NSPersistentStoreDescription()
    description.type = NSInMemoryStoreType
    container.persistentStoreDescriptions = [description]

    container.loadPersistentStores(completionHandler: { (storeDescription, error) in
        if let error = error as NSError? {
            fatalError("Unresolved error \(error), \(error.userInfo)")
        }
    })

    return container.viewContext
}
Run Code Online (Sandbox Code Playgroud)

我有一个扩展来查找给出谓词的第一个项目.

extension NSManagedObject {
    class func first(with predicate: NSPredicate?, in context: NSManagedObjectContext) throws -> Self? {
        return try _first(with: predicate, in: context)
    }

    fileprivate class func _first<T>(with predicate: NSPredicate?, in context: NSManagedObjectContext) throws -> T? where T: NSFetchRequestResult, T: NSManagedObject {
        let fetchRequest = self.fetchRequest()
        fetchRequest.fetchLimit = 1
        fetchRequest.predicate = predicate
        let results = try context.fetch(fetchRequest)
        return results.first as? T
    }
}
Run Code Online (Sandbox Code Playgroud)

这是问题所在.

该测试通过:

func testExample() {
    let context = setupInMemoryManagedObjectContext()
    let entity = try! Entity.first(with: nil, in: context)
    XCTAssertEqual(entity, nil)
}
Run Code Online (Sandbox Code Playgroud)

但是如果在didFinishLaunchingWithOptions中触发了persistentContainer加载

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    // Override point for customization after application launch.
    let context = persistentContainer.viewContext // end of laziness
    return true
}
Run Code Online (Sandbox Code Playgroud)

然后我收到以下错误

失败:捕获"NSInvalidArgumentException","executeFetchRequest:error:不是有效的NSFetchRequest".

该错误来自_first()中的这一行

let fetchRequest = self.fetchRequest() // fetchRequest  is <uninitialized> in that case
Run Code Online (Sandbox Code Playgroud)

但是如果我先修改测试来创建一个实体,那么测试再次运行正常(XCTAssertEqual当然是不同的......):

func testExample() {
    let context = setupInMemoryManagedObjectContext()
    let firstEntity = Entity(context: context)
    let entity = try! Entity.first(with: nil, in: context)
    XCTAssertEqual(entity, firstEntity)
}
Run Code Online (Sandbox Code Playgroud)

因此,出于某种原因,创建一个新实体(不保存上下文)似乎会使事情恢复正常.

我想我的测试堆栈设置搞砸了,但我还没弄清楚原因.你了解正在发生的事情以及设置的正确方法是什么?

我正在使用Xcode 8.3,Swift 3.1,部署目标是iOS 10.3.

Mat*_*ewS 2

我不熟悉类函数fetchRequest,并且在查找此问题之前不知道它的存在。

查阅了 一些 参考资料,但不得不承认我仍然不明白发生了什么。

最近,我一直在使用 Xcode 创建的自动生成的类,该类为每个实体包含一个fetchRequest如下所示的方法:

@nonobjc public class func fetchRequest() -> NSFetchRequest<User> {
    return NSFetchRequest<Entity>(entityName: "Entity")
}
Run Code Online (Sandbox Code Playgroud)

使用它作为格式,我将_first函数中的一行更改为:

fileprivate class func _first<T>(with predicate: NSPredicate?, in context: NSManagedObjectContext) throws -> T? where T: NSFetchRequestResult, T: NSManagedObject {

    // let fetchRequest = self.fetchRequest()
    // (note that this wouldn't work if the class name and entity name were not the same)
    let fetchRequest = NSFetchRequest<T>(entityName: T.entity().managedObjectClassName)

    fetchRequest.fetchLimit = 1
    fetchRequest.predicate = predicate
    let results = try context.fetch(fetchRequest)
    return results.first
}
Run Code Online (Sandbox Code Playgroud)

对我来说,这可以防止错误 - 我发布此内容是为了看看它是否对您有帮助,但我将不得不进行更多调查以了解其原理