Reputation: 20058
When I try to use Core Data with NSInMemoryStoreType
for unit testing I always get this error:
Failed to find a unique match for an NSEntityDescription to a managed object subclass
This is my object to create the core data stack:
public enum StoreType {
case sqLite
case binary
case inMemory
.................
}
public final class CoreDataStack {
var storeType: StoreType!
public init(storeType: StoreType) {
self.storeType = storeType
}
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "Transaction")
container.loadPersistentStores(completionHandler: { (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() {
for store in persistentContainer.persistentStoreCoordinator.persistentStores {
guard let url = store.url else { return }
try! persistentContainer.persistentStoreCoordinator.remove(store)
try! FileManager.default.removeItem(at: url)
}
}
}
And this is how I am using it inside my unit test project:
class MyTests: XCTestCase {
var context: NSManagedObjectContext!
var stack: CoreDataStack!
override func setUp() {
stack = CoreDataStack(storeType: .inMemory)
context = stack.context
}
override func tearDown() {
stack.reset()
context = nil
}
}
From what I read here which seems to be the same issue that I have, I have to cleanup everything after every test, which I (think) I am doing.
Am I not cleaning up correctly ? Is there another way to do this ?
Upvotes: 1
Views: 1358
Reputation: 1051
I know this question is old, however, I've encountered this problem recently and didn't find an answer elsewhere.
Building on @JamesBedford 's answer, a way to setup your Core Data stack is:
CoreDataStack
in your app across both the app and test targets. Don't create new instances in your test target. In your app target, you could use a singleton as James suggests. Or, if you are keeping a strong reference to your Core Data stack in the AppDelegate and initialising at launch, provide a convenience static property in your app target to access from your test target. Something like:extension CoreDataStack
static var shared: CoreDataStack {
(UIApplication.shared.delegate as! AppDelegate).stack
}
}
"persistent_store_type"
, value = "in_memory"
. Then, at runtime, inside your CoreDataStack
initialiser, you can check for this environment variable using ProcessInfo
.final class CoreDataStack {
let storeType: StoreType
init() {
if ProcessInfo.processInfo.environment["persistent_store_type"] == "in_memory" {
self.storeType = .inMemory
} else {
self.storeType = .sqlLite
}
}
}
From here your test target will now use the .inMemory persistent store type and won't create the SQLLite store. You can even add a unit test asserting so :)
Upvotes: 1
Reputation: 28962
Is the CoreDataStack
class initialised in your application? For instance, in an AppDelegate
class? When the unit test is run it will initialise the AppDelegate
some time before the test is ran. I believe this is so that your tests can call into anything from the app in order to test it, as per the line @testable import MyApp
. If you're initialising a Core Data stack via your AppDelegate
and in MyTests
then you will be loading the Core Data stack twice.
Just to note, having two or more NSPersistentContainer
instances means two or more NSManagedObjectModel
instances will be loaded into memory, which is what causes the issue. Both models are providing additional NSManagedObject
subclasses at runtime. When you then try to use one of these subclasses the runtime doesn't know which to use (even though they're identical, it just sees that they have the same name). I think it'd be better if NSManagedObjectModel
could handle this case, but it's currently up to the developer to ensure there's never more than one instance loaded.
Upvotes: 1