我有一个问题,我的核心数据设置单元测试。
我在我的AppDelegate中使用默认/新的核心数据堆栈设置
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)
}
但是如果persistentContainer加载是在didFinishLaunchingWithOptions中触发的
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
}
然后,我得到以下错误
失败:捕捉到"NSInvalidArgumentException“、"executeFetchRequest:error:不是有效的NSFetchRequest”。
错误具体来自于_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、SWIFT3.1,部署目标是iOS 10.3。
发布于 2017-04-09 07:43:54
看起来这是单元测试目标中的核心数据问题(可能在使用泛型时也是如此)。我在工作中也遇到过,这是我们的解决方案:
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)
发布于 2017-04-07 19:57:32
我不熟悉类函数fetchRequest
,直到查找这个问题才知道它的存在。
我抬头看 一些 参考文献,但不得不承认我仍然不明白发生了什么。
最近,我一直在使用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
}
对我来说,这可以防止错误--我发这篇文章是为了看看它是否对你有帮助,但我需要进一步研究一下,才能理解为什么会这样做。
https://stackoverflow.com/questions/43231873
复制相似问题