首页 > 解决方案 > NSPersistentContainer 将加载到应用程序中,不会加载到测试目标中

问题描述

这个问题首先出现在我的应用程序项目中,但让我感到困惑的是,我试图通过切换到遇到相同问题的 Swift 包来简化事情。

在我的项目中,我有一个名为“Model.xcdatamodeld”的数据模型文件(尽管无论名称如何,都会出现同样的问题)。当我在正在运行的应用程序中调用以下 init 方法时,一切运行正常:

    init(title: String, then completion: @escaping ()->()) {
    self.container = NSPersistentContainer(name: title)
    
    container.loadPersistentStores { description, error in
        // don't worry about retaining self, this object lives the whole life of the app
        
        print(description)
        
        if let error = error {
            print("Error loading persistent store named \(title): \(error.localizedDescription)")
            return
        }
                    
        completion()
    }
}

不打印错误,调用完成块,并且我可以在应用程序的剩余生命周期内访问持久容器的 viewContext。

但是当我在我的测试目标中运行相同的代码时,我会得到非常不同的行为。永远不会输入闭包,当我尝试访问容器的 viewContext 时,它为零。

我已经将测试简化为更简单的东西:

    func testLoadingMomD() {
    
    let expectation = XCTestExpectation(description: "core data model is set up")

    let container = NSPersistentContainer(name: "Model")
    container.loadPersistentStores { description, error in
        if let error = error {
            print(error.localizedDescription)
        }
        
        print(description)
        
        expectation.fulfill()

    }
    
    wait(for: [expectation], timeout: 10.0)

}

当我运行这个测试时,我在控制台中得到以下输出:

Test Case '-[GoldfishCoreDataTests.GoldfishCoreDataTests testLoadingMomD]' started.
2020-12-03 22:47:07.695319-0500 xctest[2548:72372] [error] error:  Failed to load model named Model
CoreData: error:  Failed to load model named Model
/Users/joseph/Documents/QuickScheduling/GoldfishCoreData/GoldfishCoreData/Tests/GoldfishCoreDataTests/GoldfishCoreDataTests.swift:23: error: -[GoldfishCoreDataTests.GoldfishCoreDataTests testLoadingMomD] : Asynchronous wait failed: Exceeded timeout of 10 seconds, with unfulfilled expectations: "core data model is set up".
Test Case '-[GoldfishCoreDataTests.GoldfishCoreDataTests testLoadingMomD]' failed (10.021 seconds).
Test Suite 'GoldfishCoreDataTests' failed at 2020-12-03 22:47:17.714.
     Executed 1 test, with 1 failure (0 unexpected) in 10.021 (10.021) seconds
Test Suite 'GoldfishCoreDataTests.xctest' failed at 2020-12-03 22:47:17.715.
     Executed 1 test, with 1 failure (0 unexpected) in 10.021 (10.022) seconds
Test Suite 'All tests' failed at 2020-12-03 22:47:17.715.
     Executed 1 test, with 1 failure (0 unexpected) in 10.021 (10.022) seconds
Program ended with exit code: 1

如果我在闭包内的任何地方放置一个断点,它就永远不会触发。

当然,我已经验证了 Model.xcdatamodeld 文件在适当的目标中,我什至删除了 Model.xcdatamodeld 并重新创建它而没有任何更改。正如我所说,这发生在我的应用程序项目和一个单独的 Swift 包中,它只包含这个测试代码和 Model.xcdatamodeld 文件。

为了更好地衡量,我在这个项目中有一个 iOS 和一个 macOS 目标,无论我测试哪个目标,我都会得到相同的行为。

在 XCTest 目标中使用 NSPersistentContainer 是不可能的吗?

标签: iosswiftxcodemacoscore-data

解决方案


所以简短的回答是 NSPersistentContainer 只在主包中查找其模型文件,除非另有说明。尝试在不是应用程序目标的目标(如测试目标)中使用它,它不会找到模型文件。您必须明确告诉它自己使用哪个模型文件。因此,您必须使用 Bundle 来查找类型为“momd”的资源。

这是我想出的:

enum LoadingError: Error {
    case doesNotExist(String)
    case corruptObjectModel(URL)
}

init?(title: String, then completion: @escaping (Error?)->()) {
    
    guard let modelURL = Bundle(for: type(of: self)).url(forResource: title, withExtension: "momd") else {
        completion(LoadingError.doesNotExist(title))
        return nil
    }

    guard let managedObjectModel = NSManagedObjectModel(contentsOf: modelURL) else {
        completion(LoadingError.corruptObjectModel(modelURL))
        return nil
    }
    
    self.container = NSPersistentContainer(name: title, managedObjectModel: managedObjectModel)
    
    container.loadPersistentStores { description, error in
        // don't worry about retaining self, this object lives the whole life of the app
                    
        if let error = error {
            print("Error loading persistent store named \(title): \(error.localizedDescription)")
            DispatchQueue.main.async {
                completion(error)
            }
            return
        }
                               
        completion(nil)
    }
}

顺便说一句,显然您可以通过继承 NSPersistenContainer 来避免这种情况(请参阅https://asciiwwdc.com/2018/sessions/224?q=nspersistentcontainer)。由于其他原因,这不是我的首选选项,但它可能对其他人有用。


推荐阅读