Reputation: 3203
I'm using Core Data in my very small and simple app. It works fine. I'd like to set up some unit tests before I start growing the app and making it more complex. There are some hoops you have to jump through to get this to happen, but I've found numerous useful answers on SE to get me through those hoops. The basic idea is that you have to create the object model manually. Easy enough; I adapted this code from one of the answers linked above, and problem solved.
let managedObjectModel = NSManagedObjectModel.mergedModel(from: [Bundle.main])!
let persistentStoreCoordinator = NSPersistentStoreCoordinator(managedObjectModel: managedObjectModel)
try! persistentStoreCoordinator.addPersistentStore(ofType: NSInMemoryStoreType, configurationName: nil, at: nil, options: nil)
let managedObjectContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
managedObjectContext.persistentStoreCoordinator = persistentStoreCoordinator
NSEntityDescription.entity(forEntityName: "VAgentEditor", in: managedObjectContext)!
Great. But I also want to run my tests without running the app, just to test my low-level logic. More hoops to jump through. Lots of useful answers on this too, problem solved.
The trouble happens when I try to access Core Data and run without the app window. Naively combining the solutions to both problems, I get nil back from the call to NSEntityDescription.entity()
. I've tweaked the above code in every way I can think of, but to no avail. If I can hack it enough get non-nil back from the call to entity()
, I end up getting messages that VAgentEditor
can't be found, or when I try to save the context to disk, I get a file that looks like something worked halfway --
lots of stuff in it, but no VAgentEditor
like I get when I try only one of the two working scenarios described above.
When I try to point the unit test to the main app's bundle, I get various complaints about the bundle not being found, or the unit test not being able to use both its own bundle and the app bundle, and a hundred other complaints that I don't remember after hacking at this for a couple of days.
Is there a way to get my unit test to use CoreData, access the correct bundle to get at the classes I defined in Interface Builder, and run the test without running the main app?
Upvotes: 1
Views: 152
Reputation: 3040
Everything is easier, if you use CoreStore for database handling. You set up your database with this code:
let dataStack: DataStack = {
let dataStack = DataStack(xcodeModelName: "ModelName")
do {
try dataStack.addStorageAndWait()
} catch let error {
XCTFail("Cannot set up database storage: \(error)")
}
return dataStack
}()
Assuming that you have the following database object:
class User: NSManagedObject {
@NSManaged var name: String
func rename(name: String, transaction: BaseDataTransaction?) {
guard let user = transaction.edit(self) else {
return
}
user.name = name
}
}
You can run a unit test like this:
class FileTests: XCTestCase {
func testRename() {
// 1. Arrange
var user: User?
do {
try dataStack.perform(synchronous: { transaction in
user = transaction.create(Into<User>())
user!.name = "Test"
})
} catch let error {
XCTFail("Cannot perform database transaction: \(error)")
}
// 2. Action
do {
try dataStack.perform(synchronous: { transaction in
guard let user = transaction.edit(user!) else {
return
}
user.rename(name: "Renamed", transaction: transaction)
})
} catch let error {
XCTFail("Cannot perform database transaction: \(error)")
}
// 3. Assert
do {
try dataStack.perform(synchronous: { transaction in
guard let user = transaction.edit(user!) else {
return
}
XCTAssertEqual(user.name, "Renamed")
})
} catch let error {
XCTFail("Cannot perform database transaction: \(error)")
}
}
}
This is just the beginning. You can streamline and improve the whole process but I hope you get the idea.
Upvotes: 1