I am currently unit testing a layer that interacts with Core Data. It saves, deletes, and updates an Item object. However, my test that attempts to save a couple of Items and then perform a batch deletion keeps failing.
This is Item:
extension Item {
    // MARK: - Properties
    @NSManaged public var date: NSDate
    @NSManaged public var isTaxable: Bool
    @NSManaged public var name: String
    @NSManaged public var price: NSDecimalNumber
    @NSManaged public var quantity: Double
    // MARK: - Fetch Requests
    @nonobjc public class func fetchRequest() -> NSFetchRequest<Item> { return NSFetchRequest<Item>(entityName: "Item") }
    // MARK: - Validation
    // Manual validation for `Decimal` values is needed. A radar is still open, which is located at https://openradar.appspot.com/13677527.
    public override func validateValue(_ value: AutoreleasingUnsafeMutablePointer<AnyObject?>, forKey key: String) throws {
        if key == "price", let decimal = value.pointee as? Decimal { if decimal < Decimal(0.01) { throw NSError(domain: NSCocoaErrorDomain, code: 1620, userInfo: ["Item": self]) } }
        if key == "quantity", let double = value.pointee as? Double { if double == 0 { throw NSError(domain: NSCocoaErrorDomain, code: 1620, userInfo: ["Item": self]) } }
    }
}
This is the object that interacts with Core Data, CoreDataStack:
internal class CoreDataStack {
    // MARK: - Properties
    private let modelName: String
    internal lazy var storeContainer: NSPersistentContainer = {
        let container = NSPersistentContainer(name: self.modelName)
        container.loadPersistentStores { (storeDescription, error) in if let error = error as NSError? { fatalError("Unresolved error \(error), \(error.userInfo)") } }
        return container
    }()
    internal lazy var managedContext: NSManagedObjectContext = { return self.storeContainer.viewContext }()
    // MARK: - Initialization
    internal init(modelName: String = "Cart") { self.modelName = modelName }
    // MARK: - Saving
    internal func saveContext() throws {
        guard managedContext.hasChanges else { return }
        do { try managedContext.save() } catch let error as NSError { throw error }
    }
}
This is the object that manages persistence with Core Data:
internal final class ItemPersistenceService {
    // MARK: - Properties
    private let coreDataStack: CoreDataStack
    // MARK: - Initialization
    internal init(coreDataStack: CoreDataStack) {
        self.coreDataStack = coreDataStack
        print("init(coreDataStack:) - ItemPersistenceService")
    }
    // MARK: - Saving
    @discardableResult internal func saveItem(withInformation information: ItemInformation) throws -> Item {
        let item = Item(context: coreDataStack.managedContext)
        item.name = information.name
        item.quantity = information.quantity
        item.price = information.price as NSDecimalNumber
        item.date = information.date as NSDate
        item.isTaxable = information.isTaxable
        do {
            try coreDataStack.saveContext()
        } catch let error as NSError {
            throw error
        }
        return item
    }
    // MARK: - Deleting
    internal func delete(item: Item) throws {
        coreDataStack.managedContext.delete(item)
        do {
            try coreDataStack.saveContext()
        } catch let error as NSError {
            throw error
        }
    }
    internal func deleteAllItems() throws {
        let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: Item.description())
        let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest)
        do {
            try coreDataStack.managedContext.persistentStoreCoordinator?.execute(deleteRequest, with: coreDataStack.managedContext)
        } catch let error as NSError {
            throw error
        }
    }
     // MARK: - Fetching
    internal func itemsCount() throws -> Int {
        let fetchRequest = NSFetchRequest<NSNumber>(entityName: Item.description())
        fetchRequest.resultType = .countResultType
        do {
            let result = try coreDataStack.managedContext.fetch(fetchRequest)
            guard let count = result.first?.intValue else { fatalError("Invalid result") }
            return count
        } catch {
            throw error
        }
    }
}
This is the CoreDataStack subclass that I use for testing, which contains an in-memory store:
internal final class TestCoreDataStack: CoreDataStack {
    // MARK: - Initialization
    internal override init(modelName: String = "Cart") {
        super.init(modelName: modelName)
        let persistentStoreDescription = NSPersistentStoreDescription()
        persistentStoreDescription.type = NSInMemoryStoreType
        let container = NSPersistentContainer(name: modelName)
        container.persistentStoreDescriptions = [persistentStoreDescription]
        container.loadPersistentStores { (storeDescription, error) in
            if let error = error as NSError? { fatalError("Unresolved error \(error), \(error.userInfo)") }
            self.storeContainer = container
        }
    }
}
Finally, this is the test that keeps failing:
internal func test_ItemPersistenceService_Delete_All_Managed_Object_Context_Saved() {
    do {
        try service.saveItem(withInformation: information)
        try service.saveItem(withInformation: information)
    } catch { XCTFail("Expected `Item`") }
    expectation(forNotification: .NSManagedObjectContextDidSave, object: coreDataStack.managedContext) { (notification) in return true }
    do { try service.deleteAllItems() } catch { XCTFail("Expected deletion") }
    waitForExpectations(timeout: 2.0) { error in XCTAssertNil(error, "Expected save to occur") }
}
Questions
Is NSInMemoryStoreType incompatible with NSBatchDeleteRequest?
If not, then what am I doing incorrectly that is causing my test to fail repeatedly?
You can always create a persistent store of type SQLite and store it at /dev/null. Here's the code to do it on a swift XCTest class:
var container: NSPersistentContainer!
override func setUp() {
    super.setUp()
    container = NSPersistentContainer(name: "ModelName")
    container.persistentStoreDescriptions[0].url = URL(fileURLWithPath: "/dev/null")
    container.loadPersistentStores { (description, error) in
        XCTAssertNil(error)
    }
}
I was hoping to use the same method for deleting a large number of objects efficiently too, but this page states that the NSBatchDeleteRequest is only compatible with SQLite persistent store types, In-memory store types are not supported.
https://developer.apple.com/library/content/featuredarticles/CoreData_Batch_Guide/BatchDeletes/BatchDeletes.html
Important: Batch deletes are only available when you are using a SQLite persistent store
The different persistent store types are listed here:
https://developer.apple.com/documentation/coredata/nspersistentstorecoordinator/persistent_store_types
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