问题
tl;dr:
- clone the project: https://github.com/Jasperav/CoreDataInMemoryFail
- Run the test and see it fail. Why does my in memory container not have any data and how can I make sure it will have data?
Long:
I have a sqlite file with filled data and I have an in-memory database in CoreData. Some code:
// ...
func createInMemoryPerformanceTestDatabase() -> NSPersistentContainer {
let url = createPathToSomeSQLiteFile()
let container = NSPersistentContainer(name: dataModelName, managedObjectModel: objectModel)
let description = NSPersistentStoreDescription(url: url)
description.type = NSInMemoryStoreType
container.persistentStoreDescriptions = [description]
container.loadPersistentStores { description, error in
XCTAssertNil(error)
}
return container
}
// ...
Although the sqlite file has data inside it, I don't see it back inside my contexts I create with container
.
When I create an in-memory database with CoreData pointing to a sqlite file with data, I don't see any results when querying the database. I want to see the data inside the sqlite file. The data should just load all in memory. This is for testing purposes.
回答1:
The problem with what you have tried was that you set the type of your storeDescription as NSInMemoryStoreType before loading them into the container. Since, the type of storeDescription is stated as NSInMemoryStoreType the api won't read and populate data from the file URL you have provided. In order for the api to read the data from the file url, the type of storeDescription must be the one defined by initialising with the initialiser init(url: URL)
which is SQLite in your case.
However if you want to have a persistentStore of type NSInMemoryStoreType with data read from the file url, you can migrate the persistentStores of your persistentContainer with NSInMemoryStoreType type using function migratePersistentStore:toURL:options:withType:error:
. You can try out the code snippet below.
import CoreData
import XCTest
@testable import CoreDataInMemoryFail
class CoreDataInMemoryFailTests: XCTestCase {
private func createContainer(modify: (NSPersistentContainer) -> ()) -> NSPersistentContainer {
let bundle = Bundle(for: type(of: self))
let path = bundle.path(forResource: "InMemoryDatabase", ofType: "sqlite")!
let url = URL(fileURLWithPath: path)
let persistentContainer = createPersistentContainer(dataModelName: "InMemoryDatabase")
let storeDescription = NSPersistentStoreDescription(url: url)
persistentContainer.persistentStoreDescriptions = [storeDescription]
persistentContainer.loadPersistentStores { description, error in
XCTAssertEqual(storeDescription.type, description.type)
XCTAssertNil(error)
}
modify(persistentContainer)
return persistentContainer
}
func testFail() {
let persistentContainer = createContainer(modify: { _ in })
let inMemoryContainer = createContainer { persistentContainer in
let coordinator = persistentContainer.persistentStoreCoordinator
coordinator.persistentStores.forEach { (persistentStore) in
do {
try coordinator.migratePersistentStore(persistentStore, to: NSPersistentContainer.defaultDirectoryURL(), options: nil, withType: NSInMemoryStoreType)
} catch {
print("Error while migrating persistentStore")
}
}
}
let persistentContainerCoordinator = persistentContainer.persistentStoreCoordinator
persistentContainerCoordinator.persistentStores.forEach { (persistentStore) in
XCTAssertEqual(persistentStore.type, "SQLite")
}
let inMemoryContainerCoordinator = inMemoryContainer.persistentStoreCoordinator
inMemoryContainerCoordinator.persistentStores.forEach { (persistentStore) in
XCTAssertEqual(persistentStore.type, NSInMemoryStoreType)
}
let fetchRequest: NSFetchRequest<Person> = Person.fetchRequest()
let persistentContainerCount = (try! persistentContainer.viewContext.fetch(fetchRequest)).count
let inMemoryContainerCount = (try! inMemoryContainer.viewContext.fetch(fetchRequest)).count
XCTAssertEqual(8, persistentContainerCount)
XCTAssertEqual(persistentContainerCount, inMemoryContainerCount)
}
}
In the above snippet, I have also added asserts to verify whether persistentStore type is NSInMemoryStoreType in your inMemoryContainer and SQLite in your persistentContainer. Hope it helps.
回答2:
The InMemoryType is not loading the date from your url as the other answer suggests. If you need to load the data from the file then please use the Migrate approach mentioned however if you only need to fill it with random data for testing purposes then here is another solution.
import CoreData
import XCTest
@testable import CoreDataInMemoryFail
class CoreDataInMemoryFailTests: XCTestCase {
var persistentContainer: NSPersistentContainer!
var inMemoryContainer: NSPersistentContainer!
override func setUp() {
super.setUp()
persistentContainer = createContainer(modify: { _ in })
inMemoryContainer = createContainer { storeDescription in
storeDescription.type = NSInMemoryStoreType
}
initStubs()
}
override class func tearDown() {
super.tearDown()
}
private func createContainer(modify: (NSPersistentStoreDescription) -> ()) -> NSPersistentContainer {
let bundle = Bundle(for: type(of: self))
let path = bundle.path(forResource: "InMemoryDatabase", ofType: "sqlite")!
let url = URL(fileURLWithPath: path)
let fileManager = FileManager.default
let uuid = UUID().uuidString
let saveDirectory = fileManager
.urls(for: .cachesDirectory, in: .userDomainMask)[0]
.appendingPathComponent(uuid)
let saveLocation = saveDirectory.appendingPathComponent(url.lastPathComponent)
try! fileManager.createDirectory(at: saveDirectory, withIntermediateDirectories: false)
try! fileManager.copyItem(at: url, to: saveLocation)
let persistentContainer = createPersistentContainer(dataModelName: "InMemoryDatabase")
let storeDescription = NSPersistentStoreDescription(url: saveLocation)
modify(storeDescription)
print("TYPE OF STORE IS: \(storeDescription)")
persistentContainer.persistentStoreDescriptions = [storeDescription]
persistentContainer.loadPersistentStores { description, error in
XCTAssertEqual(storeDescription.type, description.type)
XCTAssertNil(error)
}
return persistentContainer
}
func initStubs() {
func inserPerson( age: Int32) -> Person? {
let obj = NSEntityDescription.insertNewObject(forEntityName: "Person", into: inMemoryContainer.viewContext)
obj.setValue(age, forKey: "age")
return obj as? Person
}
_ = inserPerson(age: 1)
_ = inserPerson(age: 2)
_ = inserPerson(age: 3)
_ = inserPerson(age: 4)
_ = inserPerson(age: 5)
do {
try inMemoryContainer.viewContext.save()
} catch {
print("create fakes error \(error)")
}
}
func removeData() {
let fetchRequest:NSFetchRequest<NSFetchRequestResult> = NSFetchRequest<NSFetchRequestResult>(entityName: "Person")
let objs = try! inMemoryContainer.viewContext.fetch(fetchRequest)
for case let obj as NSManagedObject in objs {
inMemoryContainer.viewContext.delete(obj)
}
try! inMemoryContainer.viewContext.save()
}
func testFail() {
let fetchRequest: NSFetchRequest<Person> = Person.fetchRequest()
let persistentContainerCount = (try! persistentContainer.viewContext.fetch(fetchRequest)).count
let inMemoryContainerCount = (try! inMemoryContainer.viewContext.fetch(fetchRequest)).count
XCTAssertEqual(8, persistentContainerCount)
XCTAssertEqual(5, inMemoryContainerCount)
}
}
More info can be found here
来源:https://stackoverflow.com/questions/58584486/in-memory-coredata-container-is-empty-when-initialized