NSPersistentContainer & UnitTests with iOS10

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.

+5
source share
2 answers

This seems to be a problem with the master data in Unit test target (possibly also when generics are used). I also ran into this at work, this is the solution we have:

 func fetchObjects<Entity: NSManagedObject>(type: Entity.Type, predicate: NSPredicate? = nil, sortDescriptors: [NSSortDescriptor]? = nil, fetchLimit: Int? = nil) throws -> [Entity] { /* NOTE: This fetch request is constructed this way, because there is a compiler error when using type.fetchRequest() or Entity.fetchRequest() only in the test target, which makes the fetchRequest initialize to nil and crashes the fetch at runtime. */ let name = String(describing: type) let fetchRequest: NSFetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: name) 
+3
source

I am not familiar with the function of the fetchRequest class and did not know that it exists until it considers this issue.

I looked a little , but I must admit that I still do not understand what is happening.

Recently, I used the automatically generated classes that Xcode creates, which for each object includes the fetchRequest method, which looks like this:

 @nonobjc public class func fetchRequest() -> NSFetchRequest<User> { return NSFetchRequest<Entity>(entityName: "Entity") } 

Using this as a format, I changed the line in your _first function to this:

 fileprivate class func _first<T>(with predicate: NSPredicate?, in context: NSManagedObjectContext) throws -> T? where T: NSFetchRequestResult, T: NSManagedObject { // let fetchRequest = self.fetchRequest() // (note that this wouldn't work if the class name and entity name were not the same) let fetchRequest = NSFetchRequest<T>(entityName: T.entity().managedObjectClassName) fetchRequest.fetchLimit = 1 fetchRequest.predicate = predicate let results = try context.fetch(fetchRequest) return results.first } 

Which prevents me from making a mistake - I am posting this to find out if this helps you, but I will have to research something else to understand why this works.

+2
source

Source: https://habr.com/ru/post/1266359/


All Articles