I created mocked version of CoreData stack
import Foundation
import CoreData
@testable import Companion
final class MockedDatabaseStackController: DatabaseStackControllerProtocol {
let batchRequestsAvailable: Bool = false
private lazy var managedObjectModel = NSManagedObjectModel.mergedModel(from: [Bundle(for: type(of: self))])!
lazy var persistentContainer: NSPersistentContainer = {
let description = NSPersistentStoreDescription()
description.type = NSInMemoryStoreType
description.shouldAddStoreAsynchronously = false
let container = NSPersistentContainer(
name: "database",
managedObjectModel: managedObjectModel
)
container.persistentStoreDescriptions = [description]
container.loadPersistentStores { description, error in
// Check if the data store is in memory
precondition( description.type == NSInMemoryStoreType )
// Check if creating container wrong
if let error = error {
fatalError("Create an in-mem coordinator failed \(error)")
}
}
return container
}()
init() {
NotificationCenter.default
.addObserver(
self,
selector: #selector(didManagedObjectContextSave(notification:)),
name: .NSManagedObjectContextDidSave,
object: nil
)
}
@objc
private func didManagedObjectContextSave(notification: Notification) {
DispatchQueue.main.async { [weak self] in
self?.persistentContainer.viewContext.mergeChanges(fromContextDidSave: notification)
}
}
}
and I am using it to save some object:
private func executeAndSave<T>(_ executionBlock: @escaping ((NSManagedObjectContext) throws -> T)) -> Single<T> {
let persistentContainer = stackController.persistentContainer
return Single.create { observer in
persistentContainer.performBackgroundTask { context in
do {
context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
jsons.forEach {
let mo = type.init(context: context)
mo.update(withGatewayResponse: $0)
}
try context.save()
DispatchQueue.main.async {
observer(.success(result))
}
} catch {
DispatchQueue.main.async {
observer(.error(error))
}
}
}
return Disposables.create()
}
}
func save(jsons: [JSON], as type: GatewayObjectDeserializableAndSavable.Type) -> Single<Void> {
if jsons.isEmpty {
log.info("(\(type)) Nothing to save.")
return .just(())
}
log.info("DatabaseHelper will save \(type)")
return executeAndSave { context in
jsons.forEach {
let mo = type.init(context: context)
mo.update(withGatewayResponse: $0)
}
}
}
// Example of usage:
databaseHelper.save(jsons: jsons, as: Herd.self)
I created constraints in database model, for example:
But it doesn't work. Objects duplicate in database.
Note that everything works fine in main target where I use this CoreData's stack:
final class DatabaseStackController: DatabaseStackControllerProtocol {
// singleton
static let controller = DatabaseStackController()
private static let kDatabaseName = "database"
let persistentContainer: NSPersistentContainer = DatabaseStackController.buildDatabaseStack(onComplete: nil)
let batchRequestsAvailable: Bool = true
private init() {
addNSMangedObjectContextObservers()
}
private static func buildDatabaseStack(onComplete: (() -> Void)?) -> NSPersistentContainer {
let container = NSPersistentContainer(name: kDatabaseName)
container.loadPersistentStores { _, error in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
onComplete?()
}
return container
}
}
Why it doesn't work? Is NSInMemoryStoreType
not support CoreData's constraints? Is it possible to fix it?
I think you've found a bug in Core Data :(
I have a little demo project for Core Data's Unique Constraint. I confirmed that it works as expected except for some exceptions, always merging all duplicates, with the SQLite store. I then pasted in your MockedDatabaseStackController
class and used its persistentContainer.viewContext
with NSMergeByPropertyObjectTrumpMergePolicy
set. Result: It seems to merge the first set of duplicates on the first save operation but none after that. I then switched back to my Core Data stack, except I changed the store type to NSInMemoryStoreType
. Result: It also fails to work, the same as your MockedDatabaseStackController
.
The SQLite database which underlies Core Data's SQLite store supports the UNIQUE constraint in its SQL. I hope someone can prove me wrong, but, sadly, I suspect that Apple used this feature of SQLite to implement the Unique Constraint feature in Core Data, but failed to add to their documentation the fact that it only works for the SQLite store.
I've submitted this to Apple Bug Reporter: 50725935.
As to your test, I think that you should modify it to create a temporary SQLite store. There are in fact some features which are on the other side of the Venn diagram – supported by the in-memory store but not supported by the SQLite store. Testing with an in-memory store could leave holes in your test coverage.
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