简体   繁体   English

NSPersistentContainer和iOS10单元测试

[英]NSPersistentContainer & UnitTests with iOS10

I have an issue with my Core Data setup for Unit Tests. 我的单元测试核心数据设置有问题。

I use the default/new Core Data stack setup in my AppDelegate 我在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
    }()
}

And for testing I have a custom function to create a managed context 为了进行测试,我有一个自定义函数来创建托管上下文

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
}

I have an extension to find the first item given a predicate. 我有一个扩展,可以找到给定谓词的第一个项目。

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
    }
}

Here's the issue. 这是问题。

This test passes: 该测试通过:

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

but if the persistentContainer loading is triggered in didFinishLaunchingWithOptions 但如果在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
}

then I get the following error 然后我得到以下错误

failed: caught "NSInvalidArgumentException", "executeFetchRequest:error: is not a valid NSFetchRequest." 失败:捕获到“ NSInvalidArgumentException”,“ executeFetchRequest:错误:不是有效的NSFetchRequest。”

The error comes specifically from this line in _first() 错误专门来自_first()中的这一行

let fetchRequest = self.fetchRequest() // fetchRequest  is <uninitialized> in that case

But if I modify the test to create an entity first, the test runs fine again (the XCTAssertEqual is different of course…): 但是,如果我修改测试以首先创建一个实体,那么测试会再次正常运行(XCTAssertEqual当然是不同的……):

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

So for some reason, creating a new entity (without saving the context) seems to put things back in order. 因此由于某种原因,创建一个新实体(不保存上下文)似乎会使事情恢复正常。

I guess my stack setup for testing is screwed up, but I haven't figured out why. 我想我的测试堆栈设置搞砸了,但是我还没弄清楚为什么。 Do you understand what's happening and what is the proper way to set things up? 您是否了解正在发生的事情以及进行设置的正确方法是什么?

I'm using Xcode 8.3, Swift 3.1 and the deployment target is iOS 10.3. 我正在使用Xcode 8.3,Swift 3.1,部署目标是iOS 10.3。

Looks like this is a Core Data problem in the Unit test target (maybe also when generics are used). 看起来这是单元测试目标中的核心数据问题(也许在使用泛型时也是如此)。 I also encountered it at work, this is a solution we have: 我在工作中也遇到了它,这是我们的解决方案:

func fetchObjects<Entity: NSManagedObject>(type: Entity.Type,
                  predicate: NSPredicate? = nil,
                  sortDescriptors: [NSSortDescriptor]? = nil,
                  fetchLimit: Int? = nil) throws -> [Entity] {

    /*
     NOTE: This fetch request is constructed this way, because there is a compiler error
     when using type.fetchRequest() or Entity.fetchRequest() only in the test target, which
     makes the fetchRequest initialize to nil and crashes the fetch at runtime.
     */
    let name = String(describing: type)
    let fetchRequest: NSFetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: name)

I'm not familiar with the class function fetchRequest and didn't know it existed until looking it up for this question. 我不熟悉类函数fetchRequest ,直到查找此问题之前才知道它的存在。

I looked up some references but have to admit I still don't understand what's happening. 查阅了 一些 参考资料,但不得不承认我仍然不了解发生了什么。

Recently I've been using the automatically generated classes that Xcode creates that for each entity includes a fetchRequest method that looks like this: 最近,我一直在使用Xcode创建的自动生成的类, fetchRequest为每个实体都包含一个fetchRequest方法:

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

Using that as a format I changed a line in your _first function to this: 使用它作为格式,我在_first函数_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
}

Which for me prevents the error - I'm posting this to see if it helps you but I'm going to have to investigate some more to understand why this works. 这对我来说防止错误-我张贴此,看看它是否可以帮助你,但我将不得不研究一些更明白为什么这个工程。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM