2

I have a Swift app that uses CoreData. I created List entity with class MyAppTarget.List. Everything is configured properly in .xcdatamodeld file. In order to fetch my entities from persistent store, I am using NSFetchedResultsController:

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")

and it works like expected, returning array of MyAppTarget.List objects when fetching.

However, I would like to use it inside another target, for unit testing. I added List class to MyUnitTestTarget, so I can access it inside the unit test target. The problem is that the fetched results controller returns MyAppTarget.List objects, not the MyUnitTestTarget.List objects. In order to make the List entity testable, I have to make it public along with all methods that I need to use and I would like to avoid this.

I tried to change the managedObjectClassName property on NSEntityDescription:

fetchRequest.entity.managedObjectClassName = "MyUnitTestTarget.List"

but it generates exception:

failed: caught "NSInternalInconsistencyException", "Can't modify an immutable model."

The documentation states that

[...] once a description is used (when the managed object model to which it belongs is associated with a persistent store coordinator), it must not (indeed cannot) be changed. [...] If you need to modify a model that is in use, create a copy, modify the copy, and then discard the objects with the old model.

Unfortunately, I don't know how to implement this flow. I wonder if there is a way to change the managed object class name in runtime, before fetching the entities with NSFetchedResultsController?

Darrarski
  • 3,882
  • 6
  • 37
  • 59

2 Answers2

5

It occurs that solution to my problem was pretty simple. In order to make it working, I had to create a copy of managedObjectModel, edit its entities and create NSPersistentStoreCoordinator with the new model. Changing the managedObjectClassName property on NSEntityDescription instance is possible only before model to which it belongs is associated with NSPersistentStoreCoordinator.

    let testManagedObjectModel = managedObjectModel.copy() as NSManagedObjectModel
    for entity in testManagedObjectModel.entities as [NSEntityDescription] {
        if entity.name == "List" {
            entity.managedObjectClassName = "CheckListsTests.List"
        }
    }

This also solves my other problem with unit testing CoreData model entities in Swift.

Community
  • 1
  • 1
Darrarski
  • 3,882
  • 6
  • 37
  • 59
0

You can dynamically alter the class name of the NSManagedObject subclass with something like:

    let managedObjectModel = NSManagedObjectModel.mergedModelFromBundles([NSBundle.mainBundle()])!

    // Check if it is within the test environment
    let environment = NSProcessInfo.processInfo().environment as! [String : AnyObject]
    let isTestEnvironment = (environment["XCInjectBundle"] as? String)?.pathExtension == "xctest"

    // Create the module name based on product name 
    let productName:String = NSBundle.mainBundle().infoDictionary?["CFBundleName"] as! String
    let moduleName = (isTestEnvironment) ? productName + "Tests" : productName

    let newManagedObjectModel:NSManagedObjectModel = managedObjectModel.copy() as! NSManagedObjectModel

    for entity in newManagedObjectModel.entities as! [NSEntityDescription] {
        entity.managedObjectClassName = "\(moduleName).\(entity.name!)"
    }
Ronnie Liew
  • 18,220
  • 14
  • 46
  • 50