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
}
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With