Nick Kohrn
Nick Kohrn

Reputation: 6049

Unit Tests for Core Data Stack with In-Memory Store Keep Failing

I am building a personal project to allow my wife and I to monitor our grocery spending habits. The application utilizes Core Data, and I am having difficulty with my unit tests. I am testing my CoreDataStack class to ensure that its methods are saving and deleting objects.

Here is 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() {
        guard managedContext.hasChanges else {
            return
        }
        do {
            try managedContext.save()
        } catch let error as NSError {
            fatalError("Unresolved error \(error), \(error.userInfo)")
        }
    }

    // MARK: - Deleting

    internal func delete(object: NSManagedObject, from context: NSManagedObjectContext) {
        context.delete(object)
    }

    internal func delete(allObjectsForEntity entity: String, from context: NSManagedObjectContext) {
        let deleteFetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: entity)
        let batchDeleteRequest = NSBatchDeleteRequest(fetchRequest: deleteFetchRequest)
        do {
            try storeContainer.persistentStoreCoordinator.execute(batchDeleteRequest, with: context)
        } catch let error as NSError {
            fatalError("Unresolved error \(error), \(error.userInfo)")
        }
    }

}

I have created a subclass of CoreDataStack, called TestCoreDataStack, in my tests target. TestCoreDataStack uses an in-memory store so that it is destroyed when the encompassing test is finished.

Here is TestCoreDataStack:

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
    } 
}

I also have a helper class, ManagedObjectFetcher, which is included only in my tests target, simply for fetching managed objects after they are saved or deleted. This class is used so that I can verify the counts and that the correct information exists after objects have been saved or deleted.

Hers is ManagedObjectFetcher:

internal final class ManagedObjectFetcher {

    // MARK: - Properties

    private let coreDataStack: CoreDataStack

    // MARK: - Initialization

    internal init(coreDataStack: CoreDataStack) {
        self.coreDataStack = coreDataStack
    }

    // MARK: - All Records

    internal func allRecords<T: NSManagedObject>(for entity : T.Type) -> [T] {
        let request = T.fetchRequest()
        do {
            guard let results = try coreDataStack.managedContext.fetch(request) as? [T] else {
                fatalError("Invalid results")
            }
            return results
        } catch {
            fatalError("Error with request: \(error)")
        }
    }

}

CoreDataStackTests is my XCTestCase subclass for testing CoreDataStack to ensure that objects are saved or deleted.

Here is CoreDataStackTests:

internal final class CoreDataStackTests: XCTestCase {

    // MARK: - Properties

    private var coreDataStack: TestCoreDataStack!

    private var managedObjectFetcher: ManagedObjectFetcher!

    // MARK: - Test Lifecycle

    override func setUp() {
        super.setUp()
        coreDataStack = TestCoreDataStack()
        managedObjectFetcher = ManagedObjectFetcher(coreDataStack: coreDataStack)
    }

    override func tearDown() {
        coreDataStack = nil
        managedObjectFetcher = nil
        super.tearDown()
    }

    // MARK: - Saving

    // Test 1
    internal func test_CoreDataStack_Saves_GroceryItem() {
        let preResults = managedObjectFetcher.allRecords(for: GroceryItem.self)
        XCTAssertEqual(preResults.count, 0, "Expected `0`")
        let _ = GroceryItem(name: "Name", singleItemPrice: Decimal(0.99), quantity: 1, date: Date(), isTaxable: true, managedObjectContext: coreDataStack.managedContext)
        coreDataStack.saveContext()
        let postResults = managedObjectFetcher.allRecords(for: GroceryItem.self)
        XCTAssertEqual(postResults.count, 1, "Expected `1`")
    }

    // MARK: - Deleting

    // Test 2
    internal func test_CoreDataStack_Deletes_GroceryItem() {
        let initialResults = managedObjectFetcher.allRecords(for: GroceryItem.self)
        XCTAssertEqual(initialResults.count, 0, "Expected `0`")
        let item = GroceryItem(name: "Name", singleItemPrice: Decimal(0.99), quantity: 1, date: Date(), isTaxable: true, managedObjectContext: coreDataStack.managedContext)
        coreDataStack.saveContext()
        let preResults = managedObjectFetcher.allRecords(for: GroceryItem.self)
        XCTAssertEqual(preResults.count, 1, "Expected `1`")
        coreDataStack.delete(object: item, from: coreDataStack.managedContext)
        coreDataStack.saveContext()
        let postResults = managedObjectFetcher.allRecords(for: GroceryItem.self)
        XCTAssertEqual(postResults.count, 0, "Expected `0`")
    }

    // Test 3
    internal func test_CoreDataStack_Deletes_AllGroceryItems() {
        let initialResults = managedObjectFetcher.allRecords(for: GroceryItem.self)
        XCTAssertEqual(initialResults.count, 0, "Expected `0`")
        let _ = GroceryItem(name: "Name", singleItemPrice: Decimal(0.99), quantity: 1, date: Date(), isTaxable: true, managedObjectContext: coreDataStack.managedContext)
        let _ = GroceryItem(name: "Another Name", singleItemPrice: Decimal(5.99), quantity: 2, date: Date(), isTaxable: false, managedObjectContext: coreDataStack.managedContext)
        coreDataStack.saveContext()
        let preResults = managedObjectFetcher.allRecords(for: GroceryItem.self)
        XCTAssertEqual(preResults.count, 2, "Expected `2`")
        coreDataStack.delete(allObjectsForEntity: GroceryItem.description(), from: coreDataStack.managedContext)
        coreDataStack.saveContext()
        let postResults = managedObjectFetcher.allRecords(for: GroceryItem.self)
        XCTAssertEqual(postResults.count, 0, "Expected `0`")
    }

}

When executing the tests in CoreDataStackTests, I run into the following issues that I can't resolve:

This is the console output that I get after running all three tests:

Test Suite 'Selected tests' started at 2017-07-03 21:24:01.609
Test Suite 'CartTests.xctest' started at 2017-07-03 21:24:01.610
Test Suite 'CoreDataStackTests' started at 2017-07-03 21:24:01.610
Test Case '-[CartTests.CoreDataStackTests test_CoreDataStack_Deletes_AllGroceryItems]' started.
<unknown>:0: error: -[CartTests.CoreDataStackTests test_CoreDataStack_Deletes_AllGroceryItems] : failed: caught "NSInternalInconsistencyException", "Unknown command type <NSBatchDeleteRequest : resultType : 0, fetch :<NSFetchRequest: 0x6000000de450> (entity: GroceryItem; predicate: ((null)); sortDescriptors: ((null)); type: NSManagedObjectIDResultType; includesPropertyValues: NO; includesPendingChanges: NO; ) >"
(
    0   CoreFoundation                      0x000000010c90079b __exceptionPreprocess + 171
    1   libobjc.A.dylib                     0x000000010bd2e121 objc_exception_throw + 48
    2   CoreData                            0x000000010c34febf -[NSMappedObjectStore executeRequest:withContext:error:] + 303
    3   CoreData                            0x000000010c3f0f6d __65-[NSPersistentStoreCoordinator executeRequest:withContext:error:]_block_invoke + 2509
    4   CoreData                            0x000000010c3e9126 __55-[NSPersistentStoreCoordinator _routeHeavyweightBlock:]_block_invoke + 86
    5   CoreData                            0x000000010c3fcda9 gutsOfBlockToNSPersistentStoreCoordinatorPerform + 185
    6   libdispatch.dylib                   0x0000000110bfbbb9 _dispatch_client_callout + 8
    7   libdispatch.dylib                   0x0000000110c02500 _dispatch_queue_barrier_sync_invoke_and_complete + 92
    8   CoreData                            0x000000010c3e8ab5 _perform + 213
    9   CoreData                            0x000000010c3e8e3b -[NSPersistentStoreCoordinator _routeHeavyweightBlock:] + 283
    10  CoreData                            0x000000010c2f2aba -[NSPersistentStoreCoordinator executeRequest:withContext:error:] + 634
    11  Cart                                0x000000010b40b667 _T04Cart13CoreDataStackC16deleteAllObjectsySS9forEntity_So22NSManagedObjectContextC4fromtF + 391
    12  CartTests                           0x00000001240ea070 _T09CartTests013CoreDataStackB0C05test_cdE24_Deletes_AllGroceryItemsyyF + 3344
    13  CartTests                           0x00000001240eabc4 _T09CartTests013CoreDataStackB0C05test_cdE24_Deletes_AllGroceryItemsyyFTo + 36
    14  CoreFoundation                      0x000000010c884f8c __invoking___ + 140
    15  CoreFoundation                      0x000000010c884e60 -[NSInvocation invoke] + 320
    16  XCTest                              0x00000001224dd6e0 __24-[XCTestCase invokeTest]_block_invoke_2 + 451
    17  XCTest                              0x0000000122524bed -[XCUITestContext performInScope:] + 187
    18  XCTest                              0x00000001224dd4f8 -[XCTestCase invokeTest] + 254
    19  XCTest                              0x00000001224de424 __26-[XCTestCase performTest:]_block_invoke.362 + 42
    20  XCTest                              0x0000000122529432 +[XCTContext runInContextForTestCase:block:] + 163
    21  XCTest                              0x00000001224dddc0 -[XCTestCase performTest:] + 608
    22  XCTest                              0x00000001224da396 __27-[XCTestSuite performTest:]_block_invoke + 295
    23  XCTest                              0x00000001224d9f8d -[XCTestSuite _performProtectedSectionForTest:testSection:] + 26
    24  XCTest                              0x00000001224da171 -[XCTestSuite performTest:] + 214
    25  XCTest                              0x00000001224da396 __27-[XCTestSuite performTest:]_block_invoke + 295
    26  XCTest                              0x00000001224d9f8d -[XCTestSuite _performProtectedSectionForTest:testSection:] + 26
    27  XCTest                              0x00000001224da171 -[XCTestSuite performTest:] + 214
    28  XCTest                              0x00000001224da396 __27-[XCTestSuite performTest:]_block_invoke + 295
    29  XCTest                              0x00000001224d9f8d -[XCTestSuite _performProtectedSectionForTest:testSection:] + 26
    30  XCTest                              0x00000001224da171 -[XCTestSuite performTest:] + 214
    31  XCTest                              0x0000000122530625 __44-[XCTTestRunSession runTestsAndReturnError:]_block_invoke + 40
    32  XCTest                              0x00000001224ecb99 -[XCTestObservationCenter _observeTestExecutionForBlock:] + 480
    33  XCTest                              0x00000001225304c4 -[XCTTestRunSession runTestsAndReturnError:] + 281
    34  XCTest                              0x00000001224c9d43 -[XCTestDriver runTestsAndReturnError:] + 254
    35  XCTest                              0x00000001225286b7 _XCTestMain + 616
    36  CoreFoundation                      0x000000010c8a3dcc __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__ + 12
    37  CoreFoundation                      0x000000010c88833b __CFRunLoopDoBlocks + 203
    38  CoreFoundation                      0x000000010c887b34 __CFRunLoopRun + 1060
    39  CoreFoundation                      0x000000010c887499 CFRunLoopRunSpecific + 409
    40  GraphicsServices                    0x00000001121d79d7 GSEventRunModal + 62
    41  UIKit                               0x000000010cd7f0b8 UIApplicationMain + 159
    42  Cart                                0x000000010b40e637 main + 55
    43  libdyld.dylib                       0x0000000110c71771 start + 1
    44  ???                                 0x0000000000000005 0x0 + 5
)
Test Case '-[CartTests.CoreDataStackTests test_CoreDataStack_Deletes_AllGroceryItems]' failed (2636.006 seconds).
Test Case '-[CartTests.CoreDataStackTests test_CoreDataStack_Deletes_GroceryItem]' started.
<unknown>:0: error: -[CartTests.CoreDataStackTests test_CoreDataStack_Deletes_GroceryItem] : failed: caught "NSInvalidArgumentException", "executeFetchRequest:error: <null> is not a valid NSFetchRequest."
(
    0   CoreFoundation                      0x000000010c90079b __exceptionPreprocess + 171
    1   libobjc.A.dylib                     0x000000010bd2e121 objc_exception_throw + 48
    2   CoreData                            0x000000010c2f11d8 -[NSManagedObjectContext executeFetchRequest:error:] + 792
    3   CartTests                           0x00000001240eb1f7 _T09CartTests20ManagedObjectFetcherC10allRecordsSayxGxm3for_tSo09NSManagedD0CRbzlF + 247
    4   CartTests                           0x00000001240e7d86 _T09CartTests013CoreDataStackB0C05test_cdE20_Deletes_GroceryItemyyF + 182
    5   CartTests                           0x00000001240e9344 _T09CartTests013CoreDataStackB0C05test_cdE20_Deletes_GroceryItemyyFTo + 36
    6   CoreFoundation                      0x000000010c884f8c __invoking___ + 140
    7   CoreFoundation                      0x000000010c884e60 -[NSInvocation invoke] + 320
    8   XCTest                              0x00000001224dd6e0 __24-[XCTestCase invokeTest]_block_invoke_2 + 451
    9   XCTest                              0x0000000122524bed -[XCUITestContext performInScope:] + 187
    10  XCTest                              0x00000001224dd4f8 -[XCTestCase invokeTest] + 254
    11  XCTest                              0x00000001224de424 __26-[XCTestCase performTest:]_block_invoke.362 + 42
    12  XCTest                              0x0000000122529432 +[XCTContext runInContextForTestCase:block:] + 163
    13  XCTest                              0x00000001224dddc0 -[XCTestCase performTest:] + 608
    14  XCTest                              0x00000001224da396 __27-[XCTestSuite performTest:]_block_invoke + 295
    15  XCTest                              0x00000001224d9f8d -[XCTestSuite _performProtectedSectionForTest:testSection:] + 26
    16  XCTest                              0x00000001224da171 -[XCTestSuite performTest:] + 214
    17  XCTest                              0x00000001224da396 __27-[XCTestSuite performTest:]_block_invoke + 295
    18  XCTest                              0x00000001224d9f8d -[XCTestSuite _performProtectedSectionForTest:testSection:] + 26
    19  XCTest                              0x00000001224da171 -[XCTestSuite performTest:] + 214
    20  XCTest                              0x00000001224da396 __27-[XCTestSuite performTest:]_block_invoke + 295
    21  XCTest                              0x00000001224d9f8d -[XCTestSuite _performProtectedSectionForTest:testSection:] + 26
    22  XCTest                              0x00000001224da171 -[XCTestSuite performTest:] + 214
    23  XCTest                              0x0000000122530625 __44-[XCTTestRunSession runTestsAndReturnError:]_block_invoke + 40
    24  XCTest                              0x00000001224ecb99 -[XCTestObservationCenter _observeTestExecutionForBlock:] + 480
    25  XCTest                              0x00000001225304c4 -[XCTTestRunSession runTestsAndReturnError:] + 281
    26  XCTest                              0x00000001224c9d43 -[XCTestDriver runTestsAndReturnError:] + 254
    27  XCTest                              0x00000001225286b7 _XCTestMain + 616
    28  CoreFoundation                      0x000000010c8a3dcc __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__ + 12
    29  CoreFoundation                      0x000000010c88833b __CFRunLoopDoBlocks + 203
    30  CoreFoundation                      0x000000010c887b34 __CFRunLoopRun + 1060
    31  CoreFoundation                      0x000000010c887499 CFRunLoopRunSpecific + 409
    32  GraphicsServices                    0x00000001121d79d7 GSEventRunModal + 62
    33  UIKit                               0x000000010cd7f0b8 UIApplicationMain + 159
    34  Cart                                0x000000010b40e637 main + 55
    35  libdyld.dylib                       0x0000000110c71771 start + 1
    36  ???                                 0x0000000000000005 0x0 + 5
)
Test Case '-[CartTests.CoreDataStackTests test_CoreDataStack_Deletes_GroceryItem]' failed (11.969 seconds).
Test Case '-[CartTests.CoreDataStackTests test_CoreDataStack_Saves_GroceryItem]' started.
<unknown>:0: error: -[CartTests.CoreDataStackTests test_CoreDataStack_Saves_GroceryItem] : failed: caught "NSInvalidArgumentException", "executeFetchRequest:error: <null> is not a valid NSFetchRequest."
(
    0   CoreFoundation                      0x000000010c90079b __exceptionPreprocess + 171
    1   libobjc.A.dylib                     0x000000010bd2e121 objc_exception_throw + 48
    2   CoreData                            0x000000010c2f11d8 -[NSManagedObjectContext executeFetchRequest:error:] + 792
    3   CartTests                           0x00000001240eb1f7 _T09CartTests20ManagedObjectFetcherC10allRecordsSayxGxm3for_tSo09NSManagedD0CRbzlF + 247
    4   CartTests                           0x00000001240e6fe6 _T09CartTests013CoreDataStackB0C05test_cdE18_Saves_GroceryItemyyF + 182
    5   CartTests                           0x00000001240e7cb4 _T09CartTests013CoreDataStackB0C05test_cdE18_Saves_GroceryItemyyFTo + 36
    6   CoreFoundation                      0x000000010c884f8c __invoking___ + 140
    7   CoreFoundation                      0x000000010c884e60 -[NSInvocation invoke] + 320
    8   XCTest                              0x00000001224dd6e0 __24-[XCTestCase invokeTest]_block_invoke_2 + 451
    9   XCTest                              0x0000000122524bed -[XCUITestContext performInScope:] + 187
    10  XCTest                              0x00000001224dd4f8 -[XCTestCase invokeTest] + 254
    11  XCTest                              0x00000001224de424 __26-[XCTestCase performTest:]_block_invoke.362 + 42
    12  XCTest                              0x0000000122529432 +[XCTContext runInContextForTestCase:block:] + 163
    13  XCTest                              0x00000001224dddc0 -[XCTestCase performTest:] + 608
    14  XCTest                              0x00000001224da396 __27-[XCTestSuite performTest:]_block_invoke + 295
    15  XCTest                              0x00000001224d9f8d -[XCTestSuite _performProtectedSectionForTest:testSection:] + 26
    16  XCTest                              0x00000001224da171 -[XCTestSuite performTest:] + 214
    17  XCTest                              0x00000001224da396 __27-[XCTestSuite performTest:]_block_invoke + 295
    18  XCTest                              0x00000001224d9f8d -[XCTestSuite _performProtectedSectionForTest:testSection:] + 26
    19  XCTest                              0x00000001224da171 -[XCTestSuite performTest:] + 214
    20  XCTest                              0x00000001224da396 __27-[XCTestSuite performTest:]_block_invoke + 295
    21  XCTest                              0x00000001224d9f8d -[XCTestSuite _performProtectedSectionForTest:testSection:] + 26
    22  XCTest                              0x00000001224da171 -[XCTestSuite performTest:] + 214
    23  XCTest                              0x0000000122530625 __44-[XCTTestRunSession runTestsAndReturnError:]_block_invoke + 40
    24  XCTest                              0x00000001224ecb99 -[XCTestObservationCenter _observeTestExecutionForBlock:] + 480
    25  XCTest                              0x00000001225304c4 -[XCTTestRunSession runTestsAndReturnError:] + 281
    26  XCTest                              0x00000001224c9d43 -[XCTestDriver runTestsAndReturnError:] + 254
    27  XCTest                              0x00000001225286b7 _XCTestMain + 616
    28  CoreFoundation                      0x000000010c8a3dcc __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__ + 12
    29  CoreFoundation                      0x000000010c88833b __CFRunLoopDoBlocks + 203
    30  CoreFoundation                      0x000000010c887b34 __CFRunLoopRun + 1060
    31  CoreFoundation                      0x000000010c887499 CFRunLoopRunSpecific + 409
    32  GraphicsServices                    0x00000001121d79d7 GSEventRunModal + 62
    33  UIKit                               0x000000010cd7f0b8 UIApplicationMain + 159
    34  Cart                                0x000000010b40e637 main + 55
    35  libdyld.dylib                       0x0000000110c71771 start + 1
    36  ???                                 0x0000000000000005 0x0 + 5
)
Test Case '-[CartTests.CoreDataStackTests test_CoreDataStack_Saves_GroceryItem]' failed (4.826 seconds).
Test Suite 'CoreDataStackTests' failed at 2017-07-03 22:08:14.412.
 Executed 3 tests, with 3 failures (3 unexpected) in 2652.801 (2652.802) seconds
Test Suite 'CartTests.xctest' failed at 2017-07-03 22:08:14.413.
 Executed 3 tests, with 3 failures (3 unexpected) in 2652.801 (2652.803) seconds
Test Suite 'Selected tests' failed at 2017-07-03 22:08:14.413.
 Executed 3 tests, with 3 failures (3 unexpected) in 2652.801 (2652.804) seconds


Test session log:

/var/folders/n9/48n8p_ms6rs2yc8vdkhq89br0000gn/T/com.apple.dt.XCTest/IDETestRunSession-44B21AFF-A887-42A9-8EF0-23B48DBC4744/CartTests-4BFEF3B5-D9B6-47D9-BA80-6007A7991D19/Session-CartTests-2017-07-03_212349-pZbWPl.log

What am I doing incorrectly that is causing these tests to fail?

Upvotes: 4

Views: 1788

Answers (1)

Cory Juhlin
Cory Juhlin

Reputation: 384

Unfortunately, batch deletes are not supported with CoreData in-memory stores:

Important: Batch deletes are only available when you are using a SQLite persistent store.

Upvotes: 10

Related Questions