I have a problem installing Core Data for unit tests.
I am using the standard / new installation of the Core Data stack in my 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 }() }
And for testing, I have a custom function to create a managed context
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 }
I have an extension to find the first element with a given predicate.
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 } }
Here is the problem.
This test passes:
func testExample() { let context = setupInMemoryManagedObjectContext() let entity = try! Entity.first(with: nil, in: context) XCTAssertEqual(entity, nil) }
but if persistentContainer loading starts in 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 }
then i get the following error
failed: "NSInvalidArgumentException" was detected, "executeFetchRequest: error: is not a valid NSFetchRequest."
The error occurs precisely from this line in _first ()
let fetchRequest = self.fetchRequest() // fetchRequest is <uninitialized> in that case
But if I first modify the test to create an object, the test will complete fine again (XCTAssertEqual is different, of course ...):
func testExample() { let context = setupInMemoryManagedObjectContext() let firstEntity = Entity(context: context) let entity = try! Entity.first(with: nil, in: context) XCTAssertEqual(entity, firstEntity) }
Therefore, for some reason, creating a new object (without preserving the context) seems to return order.
I think my stack setup for testing is screwed up, but I did not understand why. Do you understand what is happening and what is the right way to tune the situation?
I am using Xcode 8.3, Swift 3.1, and the deployment target is iOS 10.3.