How to unit-test NSFetchedResultsController in Swift

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.

  • How do I unit test HTTP request and response using NSURLSession in iOS 7.1?
  • Can't use Swift classes inside Objective-C unit test
  • Caught NSInternalInconsistencyException request for rect at invalid indexPath
  • “No such module” when using @testable in Xcode Unit tests
  • Could not determine bundle identifier for xxxTest TEST_HOST
  • XCTest - “Test failed”
  • 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.

    Solutions Collect From Internet About “How to unit-test NSFetchedResultsController in Swift”

    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.