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)")
}
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.
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
rised by the line:
for list: List in lists {
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.
Update:
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]
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.
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]
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]
and make the List
class public. After that change, it works like expected.
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.
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.
Update:
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
It allows to seamlessly use CoreData entities in unit tests, without casting or making entity class public.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With