Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Core Data entity name is nil while running a second unit test

I am trying to add some unit tests for my Core Data code. But I always have this issue, the first test always run correctly, but the second one crashes because entity name is nil.

I also get this error:

Multiple NSEntityDescriptions claim the NSManagedObject subclass 'Gym.Exercise' so +entity is unable to disambiguate.

Failed to find a unique match for an NSEntityDescription to a managed object subclass

So my guess is that I am not doing something right in tearDown().

override func setUp() {
    super.setUp()

    coreDataStack = CoreDataStack(storeType: .inMemory)
    context = coreDataStack.context
}

override func tearDown() {
    coreDataStack.reset()
    context = nil
    super.tearDown()
}

Here is my CoreDataStack class:

final class CoreDataStack {
    var storeType: StoreType!
    public init(storeType: StoreType) {
        self.storeType = storeType
    }

    lazy var persistentContainer: NSPersistentContainer = {
        let container = NSPersistentContainer(name: "Gym")
        container.loadPersistentStores { description, error in
            if let error = error {
                fatalError("Unresolved error \(error), \(error.localizedDescription)")
            } else {
                description.type = self.storeType.type

            }
        }

        return container
    }()

    public var context: NSManagedObjectContext {
        return persistentContainer.viewContext
    }

    public func reset() {
        guard let store = persistentContainer.persistentStoreCoordinator.persistentStores.first else { fatalError("No store found")}
        guard let url = store.url else { fatalError("No store URL found")}

        try! FileManager.default.removeItem(at: url)
        NSPersistentStoreCoordinator.destroyStoreAtURL(url: url)
    }
}

And the definition to destroyStoreAtURL:

extension NSPersistentStoreCoordinator {
    public static func destroyStoreAtURL(url: URL) {
        do {
            let psc = self.init(managedObjectModel: NSManagedObjectModel())
            try psc.destroyPersistentStore(at: url, ofType: NSSQLiteStoreType, options: nil)
        } catch let e {
            print("failed to destroy persistent store at \(url)", e)
        }
    }
}

I was using this code in the past for unit testing and it works, the difference is that in the past when I setup the NSManagedObject classes in editor I used the following config:

Module - Global namespace
Codegen - Class Definition

Now I use:

Module - Current Product Module
Codegen - Manual/None

Because I want to add my classes manually.

So does anyone know why the behavior is different now ?

edit - my NSManagedObject extension helper (the error occurs inside the first line of fetch() method when trying to retrieve the entity name):

extension Managed where Self: NSManagedObject {

    public static var entityName: String {
        return entity().name!
    }

    public static func fetch(in context: NSManagedObjectContext, configurationBlock: (NSFetchRequest<Self>) -> () = { _ in }) -> [Self] {
        let request = NSFetchRequest<Self>(entityName: Self.entityName)
        configurationBlock(request)
        return try! context.fetch(request)
    }

    public static func count(in context: NSManagedObjectContext, configure: (NSFetchRequest<Self>) -> () = { _ in }) -> Int {
        let request = NSFetchRequest<Self>(entityName: entityName)
        configure(request)
        return try! context.count(for: request)
    }

    public static func findOrFetch(in context: NSManagedObjectContext, matching predicate: NSPredicate) -> Self? {
        guard let object = materializeObject(in: context, matching: predicate) else {
        return fetch(in: context) { request in
                request.predicate = predicate
                request.returnsObjectsAsFaults = false
                request.fetchLimit = 1
            }.first
        }

        return object
    }

    public static func materializeObject(in context: NSManagedObjectContext, matching predicate: NSPredicate) -> Self? {
        for object in context.registeredObjects where !object.isFault {
           guard let result = object as? Self, predicate.evaluate(with: result) else {
               continue
           }

           return result
        }

        return nil
    }

    public static func findOrCreate(in context: NSManagedObjectContext, matching predicate: NSPredicate, configure: (Self) -> ()) -> Self {
        guard let object = findOrFetch(in: context, matching: predicate) else {
           let newObject: Self = context.insertObject()
           configure(newObject)
           return newObject
        }

        return object
    }
}
like image 241
Adrian Avatar asked Dec 08 '18 10:12

Adrian


1 Answers

You should try deleting all the found instances of persistentStore instead of deleting the first.

try replacing the reset function with below:

public func reset() {
    let stores = persistentContainer.persistentStoreCoordinator.persistentStores
    guard !stores.isEmpty else {
        fatalError("No store found")
    }
    stores.forEach { store in
        guard let url = store.url else { fatalError("No store URL found")}

        try! FileManager.default.removeItem(at: url)
        NSPersistentStoreCoordinator.destroyStoreAtURL(url: url)
    }
}

And see if you still have the issue.

like image 177
Bhaumik Desai Avatar answered Nov 10 '22 15:11

Bhaumik Desai