NSPersistentContainer&单元测试与iOS10

时间:2017-04-05 12:55:20

标签: ios swift core-data 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
    }()
}

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

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
}

我有一个扩展名来查找给定谓词的第一个项目。

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

这是问题所在。

此测试通过:

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

但是如果在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
}

然后我收到以下错误

  

失败:捕获&#34; NSInvalidArgumentException&#34;,   &#34; executeFetchRequest:error:不是有效的NSFetchRequest。&#34;

错误特别来自_first()

中的这一行
let fetchRequest = self.fetchRequest() // fetchRequest  is <uninitialized> in that case

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

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

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

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

我使用的是Xcode 8.3,Swift 3.1,部署目标是iOS 10.3。

2 个答案:

答案 0 :(得分:3)

看起来这是单元测试目标中的核心数据问题(也许在使用泛型时也是如此)。我也在工作中遇到过它,这是我们的解决方案:

Handle<Map> map = Handle<Map>::cast(args[0]);

答案 1 :(得分:2)

我不熟悉班级功能fetchRequest,并且在查找此问题之前并不知道它存在。

looked up some references但必须承认我仍然不明白发生了什么。

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

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

将其用作格式我将_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
}

对我来说,这可以防止错误 - 我发布此信息是为了帮助您,但我还需要进行更多调查,以了解为什么这样做。< / p>