In Xcode 9, when creating a Single View Application, the datastore is configured by default. The persistentContainer property (an instance of NSPersistentContainer class) is initialized in AppDelegate class. The persistentContainer encapsulates the Core Data stack in the app. It handles creation of the managed object, the persistent store coordinator, and the managed object context.
The persistentContainer object has a function called defaultDirectoryURL(), which defines the location of the datastore file. I'm going to subclass NSPersistentContainer and override this function to change the datastore's file location for unit tests.
1. Creating main.swift file
Before that, we have to comment out the @UIApplicationMain attribute on the AppDelegate class.
import Foundation
import UIKit
let isRunningTest = NSClassFromString("XCTestCase") != nil
let appDelegateClass = isRunningTest ? NSStringFromClass(UnitTestAppDelegate.self) : NSStringFromClass(AppDelegate.self)
UIApplicationMain(
CommandLine.argc,
UnsafeMutableRawPointer(CommandLine.unsafeArgv)
.bindMemory(
to: UnsafeMutablePointer.self,
capacity: Int(CommandLine.argc)),
nil,
appDelegateClass
)
When launching the app, the system looks for @UIApplicationMain attribute on a class. Xcode adds the attribute on the AppDelegate class by default. The @UIApplicationMain acts as a main function, which initializes the UIApplication object (represents the current app instance) and its delegate (responds to important events in the app's life cycle) at run time.
According to Apple's documentation, if we don't use @UIApplicationMain, we must create a main.swift file at the top level that calls the NSApplicationMain(_:_:_:_) function. The third parameter is the name of UIApplication class or subclass. In the code snippets above, it's set to nil so UIApplication is assumed. The fourth parameter is the name of AppDelegate class or subclass. If the unit test is running (if the XCTestCase class exists in the bundle or not nil), UnitTestPersistentContainer (my custom class) is assigned. Otherwise, the default AppDelegate class is assigned.
2. Creating UnitTestAppDelegate class
import Foundation
import CoreData
class UnitTestAppDelegate : AppDelegate {
override var persistentContainer: NSPersistentContainer {
get {
let container = UnitTestPersistentContainer(name: "MyCalculator")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}
set {
super.persistentContainer = newValue
}
}
}
3. Creating UnitTestPersistentContainer class
import Foundation
import CoreData
class UnitTestPersistentContainer : NSPersistentContainer {
open class override func defaultDirectoryURL() -> URL {
return URL(fileURLWithPath: NSHomeDirectory() + "/Documents")
}
}
Note that the datastore file couldn't be created when I specified "~/Documents" or "/var/TMP" for the fileURLWithPath parameter. I don't know why.
References
https://developer.apple.com/documentation/coredata/nspersistentcontainer