如何在 Swift 中对 NSFetchedResultsController 进行单元测试 [英] How to unit-test NSFetchedResultsController in Swift

查看:38
本文介绍了如何在 Swift 中对 NSFetchedResultsController 进行单元测试的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个 Swift 应用程序,它使用 NSFetchedResultsController 从持久存储中获取 List 对象:

I have a Swift app that uses NSFetchedResultsController to fetch List objects from persistent store:

let fetchedResultsController: NSFetchedResultsController = ...
var error : NSError?
fetchedResultsController.performFetch(&error)
if let error = error {
    NSLog("Error: \(error)")
}
let lists: [List] = fetchedResultsController.fetchedObjects! as [List]
NSLog("lists count = \(lists.count)")
for list: List in lists {
    NSLog("List: \(list.description)")
}

它按预期工作,我将 List 对象描述打印到控制台.我想为我的应用程序编写一些单元测试,所以我创建了扩展 XCTestCase 的类.代码编译没有问题,测试运行,但不幸的是我无法在该上下文中获取 List 对象.

and it works like expected, I am getting List objects descriptions printed out to the console. I would like to write some unit tests for my app, so I created class that extends XCTestCase. The code compiles without a problem, tests runs, but unfortunately I am not able to fetch the List objects in that context.

我在控制台中得到的只是 List 对象的计数和一个致命错误:

All I am getting in the console is count of List objects and a fatal error:

lists count = 59
fatal error: NSArray element failed to match the Swift Array Element type

由线上升:

for list: List in lists {

我很确定我已经正确配置了目标,因为我可以创建 List 对象并将其插入到托管对象上下文中,而我的应用程序源代码和单元测试源代码都没有问题.我遇到的唯一问题是从测试单元获取.我想知道为什么在模拟器中运行应用程序时获取工作正常,而在单元测试期间执行时失败.

I am pretty sure I have targets configured properly, as I can create List object and insert it into managed object context without a problem from my app's source code as well as from unit test source code. The only problem I am experiencing is with fetching from test unit. I wonder why fetching is working when running the app in the simulator and fails when executed during unit test.

任何可能出错的想法将不胜感激.

Any ideas what could be wrong will be appreciated.

更新:

为了更具体地说明我的实现方式,这里是我正在使用的完整代码示例:

To be more specific how my implementation looks like, here is complete code sample that I am playing with:

var error: NSError? = nil

let urls = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)
let applicationDocumentsDirectory = urls[urls.count-1] as NSURL

let modelURL = NSBundle.mainBundle().URLForResource("CheckLists", withExtension: "momd")!
let managedObjectModel = NSManagedObjectModel(contentsOfURL: modelURL)

var coordinator: NSPersistentStoreCoordinator? = NSPersistentStoreCoordinator(managedObjectModel: managedObjectModel)
let url = applicationDocumentsDirectory.URLByAppendingPathComponent("CheckLists.sqlite")
if coordinator!.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: url, options: nil, error: &error) == nil {
    NSLog("Error1: \(error)")
    abort()
}

var managedObjectContext = NSManagedObjectContext()
managedObjectContext.persistentStoreCoordinator = coordinator

let fetchRequest = NSFetchRequest()
fetchRequest.entity = NSEntityDescription.entityForName("List", inManagedObjectContext: managedObjectContext)
fetchRequest.sortDescriptors = [ NSSortDescriptor(key: "name", ascending: true) ]

let fetchedResultsController = NSFetchedResultsController(
    fetchRequest: fetchRequest,
    managedObjectContext: managedObjectContext,
    sectionNameKeyPath: nil,
    cacheName: "ListFetchedResultsControllerCache"
)

fetchedResultsController.performFetch(&error)
if let error = error {
    NSLog("Error2: \(error)")
    abort()
}

let fetchedObjects: [AnyObject]? = fetchedResultsController.fetchedObjects
if let fetchedObjects = fetchedObjects {
    NSLog("Fetched objects count: \(fetchedObjects.count)")
    for fetchedObject in fetchedObjects {
        NSLog("Fetched object: \(fetchedObject.description)")
    }
}
else {
    NSLog("Fetched objects array is nil")
}

let fetchedLists: [List]? = fetchedResultsController.fetchedObjects as? [List]
if let fetchedLists = fetchedLists {
    NSLog("Fetched lists count: \(fetchedLists.count)")
    for fetchedList in fetchedLists {
        NSLog("Fetched list: \(fetchedList.description)")
    }
}
else {
    NSLog("Fetched lists array is nil")
}

当我从应用程序的源代码执行它时,在模拟器中运行应用程序时,控制台输出如下所示:

When I execute it from my app's source code, running the app in simulator, the console output looks like this:

Fetched objects count: 3
Fetched object: <CheckLists.List: 0x7a6866f0> (entity: List; id: 0x7a686020 <x-coredata://7A87B5BE-C2FA-4150-B9E3-879FDE07F0B9/List/p2> ; data: {
    name = "List 1";
})
Fetched object: <CheckLists.List: 0x7a686930> (entity: List; id: 0x7a686030 <x-coredata://7A87B5BE-C2FA-4150-B9E3-879FDE07F0B9/List/p1> ; data: {
    name = "List 2";
})
Fetched object: <CheckLists.List: 0x7a686970> (entity: List; id: 0x7a686040 <x-coredata://7A87B5BE-C2FA-4150-B9E3-879FDE07F0B9/List/p3> ; data: {
    name = "List 3";
})
Fetched lists count: 3
Fetched list: <CheckLists.List: 0x7a6866f0> (entity: List; id: 0x7a686020 <x-coredata://7A87B5BE-C2FA-4150-B9E3-879FDE07F0B9/List/p2> ; data: {
    name = "List 1";
})
Fetched list: <CheckLists.List: 0x7a686930> (entity: List; id: 0x7a686030 <x-coredata://7A87B5BE-C2FA-4150-B9E3-879FDE07F0B9/List/p1> ; data: {
    name = "List 2";
})
Fetched list: <CheckLists.List: 0x7a686970> (entity: List; id: 0x7a686040 <x-coredata://7A87B5BE-C2FA-4150-B9E3-879FDE07F0B9/List/p3> ; data: {
    name = "List 3";
})

然而,当我从单元测试中执行这段代码时,我得到了这个输出:

However, when I execute this code from a unit-test, I am getting this output:

Fetched objects count: 3
Fetched object: <CheckLists.List: 0x7a07df50> (entity: List; id: 0x7a07d7e0 <x-coredata://7A87B5BE-C2FA-4150-B9E3-879FDE07F0B9/List/p2> ; data: {
    name = "List 1";
})
Fetched object: <CheckLists.List: 0x7a07e190> (entity: List; id: 0x7a07d7f0 <x-coredata://7A87B5BE-C2FA-4150-B9E3-879FDE07F0B9/List/p1> ; data: {
    name = "List 2";
})
Fetched object: <CheckLists.List: 0x7a07e1d0> (entity: List; id: 0x7a07d800 <x-coredata://7A87B5BE-C2FA-4150-B9E3-879FDE07F0B9/List/p3> ; data: {
    name = "List 3";
})
Fetched lists array is nil

我希望它能更容易地理解问题出在哪里.不知何故,这个声明:

I hope it makes easier to understand where the problem is. Somehow, this statement:

let fetchedLists: [List]? = fetchedResultsController.fetchedObjects as? [List]

当应用程序在模拟器中运行时生成一个 List 对象数组,但在单元测试中执行时它无法生成 nil.

produces an array of List objects when app is run in the simulator, but it fails producing nil when executed from unit test.

推荐答案

该问题与目标配置有关.我已经通过一些解决方法解决了这个问题.

The issue is connected with targets configuration. I have solved the problem with a little workaround.

以前,为了使 List 实体类在我的单元测试目标中可访问,我已将其添加到此目标.因此,List 类在两个目标中.事实上,Swift 已知有两个 List 类,每个目标一个:MyAppTarget.ListMyUnitTestTarget.List.Reetched 结果控制器返回 MyAppTarget.List 对象的数组,但在单元测试目标中,List 被假定为 MyUnitTestTarget.List 类.这行代码就是这样:

Previously, in order to make List entity class accessible in my unit test target, I have added it to this target. So, the List class was in two targets. In fact, there were two List classes known by Swift, one for each target: MyAppTarget.List and MyUnitTestTarget.List. Retched results controller returns array of MyAppTarget.List objects, but in unit test target, List was assumed to be MyUnitTestTarget.List class. That's way this line of code:

let fetchedLists: [List]? = fetchedResultsController.fetchedObjects as? [List]

从单元测试目标执行时

产生 nil,而不是从主目标执行时的正确数组.为了解决这个问题,我只是将其更改为:

produced nil when executed from unit test target, and not the proper array as when executed from main target. To fix that I just changed it to:

let fetchedLists: [MyAppTarget.List]? = fetchedResultsController.fetchedObjects as? [MyAppTarget.List]

并使 List 类公开.更改后,它按预期工作.

and make the List class public. After that change, it works like expected.

但是,MyAppTarget.List 不能转换为 MyUnitTestTarget.List 对我来说还是有点困惑.此外,这意味着我需要公开每个实体 NSManagedObject 子类,以便在单元测试中使用它.到目前为止,我没有找到更好的解决方案.

But still, it is a little bit confusing for me that MyAppTarget.List cannot be casted to MyUnitTestTarget.List. Moreover, it means that I need to make public every entity NSManagedObject subclass in order to use it inside unit tests. So far I didn't find better solution.

也许有更好的方法来解决这个问题.我没有看到告诉 NSFetchedResultsController 它应该在主目标中返回 MyAppTarget.List 和单元测试目标中的 MyUnitTestTarget.List 的选项.对于给定的实体,它将始终使用 .xcdatamodeld 文件中的配置.此外,即使有一种方法可以在单元测试中将 MyAppTarget.List 转换为 MyUnitTestTarget.List,它仍然需要 List 类公开.

Perhaps there is a better way to solve that issue. I don't see an option to tell NSFetchedResultsController that it should return MyAppTarget.List in main target, and MyUnitTestTarget.List in unit test target. It will always use configuration from the .xcdatamodeld file for given entity. Also, even if there is a way to cast MyAppTarget.List into MyUnitTestTarget.List inside a unit test, it will still require the List class to be public.

更新:

我找到了一种在运行时更改 NSFetchedResultsController 返回的实体类的方法.这是一个更清晰和简单的解决方案:https://stackoverflow.com/a/25858758/514181

I have found a way for changing class of entities returned by NSFetchedResultsController in runtime. It's a more clear and simple solution: https://stackoverflow.com/a/25858758/514181

它允许在单元测试中无缝使用 CoreData 实体,无需强制转换或公开实体类.

It allows to seamlessly use CoreData entities in unit tests, without casting or making entity class public.

这篇关于如何在 Swift 中对 NSFetchedResultsController 进行单元测试的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆