9

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
    }
}
4
  • 1
    Please show the code where the error occurs. Commented Dec 10, 2018 at 21:58
  • 1
    @pbasdf: done, error is happening inside fetch(...) method, first line when trying to retrieve the entity name Commented Dec 11, 2018 at 8:41
  • 1
    The thing that first comes to mind, given that error, is that you might have inadvertently defined two entities with the same class in the model editor. Have you checked through all the entity definitions to ensure they have the correct class? Commented Dec 11, 2018 at 20:40
  • 1
    @pbasdf: there is only one entity added to the model, and in code its defined once, by me. So nothing generated. Commented Dec 12, 2018 at 17:23

1 Answer 1

1

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.

Sign up to request clarification or add additional context in comments.

2 Comments

Now I get the following error: NSSQLiteErrorDomain = 522 when trying to fetch a request. So when calling "return try! context.fetch(request)" inside fetch(...) method.
And when running again, the error is always when loading the persistent store, same, 522 error.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.