Add more info for using CoreStore in Unit Tests #39

Closed
opened 2025-12-29 15:22:47 +01:00 by adam · 9 comments
Owner

Originally created by @tmspzz on GitHub (Feb 9, 2016).

I think adding the following information to the README.md would be useful for people trying to use CoreStore in unit tests.

I have the following set up and I think it's quite common.

  • I create the CoreStore.defaultStack in the Unit tests
  • I use the stack in the non-test Target (i.e. by doing CoreStore.beginSynchronous)

Problem

The problem lies in the discrepancy between the managedObjectClassName reported by NSEntityDescription (gathered by CoreData from the .xcdatamodeld) and the name reported by NSStringFromClass. This causes a EXEC_BAD_ACCESS at https://github.com/JohnEstropia/CoreStore/blob/master/CoreStore/Internal/NSManagedObjectModel%2BSetup.swift#L153 .

When running Unit Tests, CoreData will read the .xcdatamodeld and fill up NSEntityDescription with managedObjectClassName equal to the name of the class that you have specified in your CoreData Model. By default in the corresponding CoreData Configuration this happens to be prefixed by .so if you specify YourClass in the model, in the configuration it will show up as .YourClass instead of just YourClass. The . means "current module". When CoreStore is building the entityName map at https://github.com/JohnEstropia/CoreStore/blob/master/CoreStore/Internal/NSManagedObjectModel%2BSetup.swift#L243 the map will hold YourModule.YourClass or YourModuleTests.YourClass depending what target your are running. However when the map is accessed from your non-test target while running the unit test NSStringFromClass will report YourModule.YourClass as key for the map at https://github.com/JohnEstropia/CoreStore/blob/master/CoreStore/Internal/NSManagedObjectModel%2BSetup.swift#L153 and the force unwrapping will cause a memory access violation.

Solution

  • Add @objc (YourClass) on top of your NSManagedObject subclass class YourClass: NSManagedObject . This will make it so that NSStringFromClass will return YourClass as opposed to YourModule.YourClass
  • In the .xcdatamodeld Configuration in the Entity -> Class section enter YourClass and not .YourClass this will remove the module name form the class
Originally created by @tmspzz on GitHub (Feb 9, 2016). I think adding the following information to the `README.md` would be useful for people trying to use CoreStore in unit tests. I have the following set up and I think it's quite common. - I create the CoreStore.defaultStack in the Unit tests - I use the stack in the non-test Target (i.e. by doing `CoreStore.beginSynchronous`) ## Problem The problem lies in the discrepancy between the `managedObjectClassName` reported by `NSEntityDescription` (gathered by CoreData from the `.xcdatamodeld`) and the name reported by `NSStringFromClass`. This causes a `EXEC_BAD_ACCESS` at https://github.com/JohnEstropia/CoreStore/blob/master/CoreStore/Internal/NSManagedObjectModel%2BSetup.swift#L153 . When running Unit Tests, CoreData will read the `.xcdatamodeld` and fill up `NSEntityDescription` with `managedObjectClassName` equal to the name of the class that you have specified in your CoreData Model. **By default in the corresponding CoreData Configuration this happens to be prefixed by** `.`so if you specify `YourClass` in the model, in the configuration it will show up as `.YourClass` instead of just `YourClass`. The `.` means "current module". When CoreStore is building the `entityName` map at https://github.com/JohnEstropia/CoreStore/blob/master/CoreStore/Internal/NSManagedObjectModel%2BSetup.swift#L243 the map will hold `YourModule.YourClass` or `YourModuleTests.YourClass` depending what target your are running. However when the map is accessed from your non-test target while running the unit test `NSStringFromClass` will report `YourModule.YourClass` as key for the map at https://github.com/JohnEstropia/CoreStore/blob/master/CoreStore/Internal/NSManagedObjectModel%2BSetup.swift#L153 and the force unwrapping will cause a memory access violation. ## Solution - Add `@objc (YourClass)` on top of your `NSManagedObject` subclass `class YourClass: NSManagedObject` . This will make it so that `NSStringFromClass` will return `YourClass` as opposed to `YourModule.YourClass` - In the `.xcdatamodeld` Configuration in the Entity -> Class section enter `YourClass` and not `.YourClass` this will remove the module name form the class
adam closed this issue 2025-12-29 15:22:47 +01:00
Author
Owner

@JohnEstropia commented on GitHub (Feb 9, 2016):

The problem I see is that your NSManagedObject subclass is declared in a specific module (YourModule) while your .xcdatamodeld is declared in both YourModule and YourModuleTests.

I suggest you either:

  1. explicitly set the entity's Module field in your .xcdatamodeld to your main module's name (Don't leave it at Current Product Module)
  2. explicitly set the entity's Module field in your .xcdatamodeld to blank (Don't leave it at Current Product Module). Then set @objc (YourClass) on your NSManagedObject subclass.
  3. make sure that the Target Membership of your NSManagedObject subclass (.swift file) and your .xcdatamodeld include the same schemes. You probably need to remove them from the Unit Test scheme.

As for including instructions in the README file, I don't think it is within the scope of CoreStore's usage. There's just too many configurations and it should be up to you to make your Swift class match your .xcdatamodeld.

@JohnEstropia commented on GitHub (Feb 9, 2016): The problem I see is that your NSManagedObject subclass is declared in a specific module (`YourModule`) while your .xcdatamodeld is declared in both `YourModule` and `YourModuleTests`. I suggest you either: 1. explicitly set the entity's `Module` field in your .xcdatamodeld to your main module's name (Don't leave it at `Current Product Module`) 2. explicitly set the entity's `Module` field in your .xcdatamodeld to blank (Don't leave it at `Current Product Module`). Then set `@objc (YourClass)` on your NSManagedObject subclass. 3. make sure that the `Target Membership` of your NSManagedObject subclass (.swift file) and your .xcdatamodeld include the same schemes. You probably need to remove them from the Unit Test scheme. As for including instructions in the README file, I don't think it is within the scope of CoreStore's usage. There's just too many configurations and it should be up to you to make your Swift class match your .xcdatamodeld.
Author
Owner

@tmspzz commented on GitHub (Feb 10, 2016):

I see how this is not strictly relate to CoreStore but maybe a "Common Pitfalls" section would help.

I am also encountering other problems related to unit test. I am getting

 *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Object's persistent store is not reachable from this NSManagedObjectContext's coordinator'

I have 2 tests in the same Unit Test class which inherits form a base class where on set up I do like so:


class NetworkServiceBaseTestCase: XCTestCase {

    override func setUp() {
        super.setUp()

        CoreStore.defaultStack = DataStack(modelName: "LiG", bundle: NSBundle(forClass: ItemsService.self))
        do {
            try CoreStore.addInMemoryStoreAndWait()
        }
        catch {
            print("Count not create in memory store")
        }
    }
}

This code is run before each and every test. Seems like the DataStack should not be initialised more than once? I would like to clear the store on every run.

@tmspzz commented on GitHub (Feb 10, 2016): I see how this is not strictly relate to CoreStore but maybe a "Common Pitfalls" section would help. I am also encountering other problems related to unit test. I am getting ``` *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Object's persistent store is not reachable from this NSManagedObjectContext's coordinator' ``` I have 2 tests in the same Unit Test class which inherits form a base class where on set up I do like so: ``` class NetworkServiceBaseTestCase: XCTestCase { override func setUp() { super.setUp() CoreStore.defaultStack = DataStack(modelName: "LiG", bundle: NSBundle(forClass: ItemsService.self)) do { try CoreStore.addInMemoryStoreAndWait() } catch { print("Count not create in memory store") } } } ``` This code is run before each and every test. Seems like the DataStack should not be initialised more than once? I would like to clear the store on every run.
Author
Owner

@JohnEstropia commented on GitHub (Feb 10, 2016):

@blender Please refer to CoreStoreTests.swift. There you'll see that the SQLite file is deleted every time:

class CoreStoreTests: XCTestCase {

    override func setUp() {

        super.setUp()
        self.deleteStores()
    }

    override func tearDown() {

        self.deleteStores()
        super.tearDown()
    }

In deleteStores()'s implementation:

private func deleteStores() {

    do {

        let fileManager = NSFileManager.defaultManager()
        try fileManager.removeItemAtURL(
            fileManager.URLsForDirectory(.ApplicationSupportDirectory, inDomains: .UserDomainMask).first!
        )
    }
    catch _ { }
}

By doing so, you can start each of your test by creating a new DataStack and overwriting any existing ones:

func testExample() {

    let stack = DataStack(modelName: "Model", bundle: NSBundle(forClass: self.dynamicType))
    CoreStore.defaultStack = stack
    do {

        try stack.addSQLiteStoreAndWait(fileName: "ConfigStore1.sqlite", configuration: "Config1", resetStoreOnModelMismatch: true)
    }

As for your module problems, did any of the suggestions I gave solved it for you?

I suggest you either:

  1. explicitly set the entity's Module field in your .xcdatamodeld to your main module's name (Don't leave it at Current Product Module)
  2. explicitly set the entity's Module field in your .xcdatamodeld to blank (Don't leave it at Current Product Module). Then set @objc (YourClass) on your NSManagedObject subclass.
  3. make sure that the Target Membership of your NSManagedObject subclass (.swift file) and your .xcdatamodeld include the same schemes. You probably need to remove them from the Unit Test scheme.
@JohnEstropia commented on GitHub (Feb 10, 2016): @blender Please refer to `CoreStoreTests.swift`. There you'll see that the SQLite file is deleted every time: ``` swift class CoreStoreTests: XCTestCase { override func setUp() { super.setUp() self.deleteStores() } override func tearDown() { self.deleteStores() super.tearDown() } ``` In `deleteStores()`'s implementation: ``` swift private func deleteStores() { do { let fileManager = NSFileManager.defaultManager() try fileManager.removeItemAtURL( fileManager.URLsForDirectory(.ApplicationSupportDirectory, inDomains: .UserDomainMask).first! ) } catch _ { } } ``` By doing so, you can start each of your test by creating a new `DataStack` and overwriting any existing ones: ``` swift func testExample() { let stack = DataStack(modelName: "Model", bundle: NSBundle(forClass: self.dynamicType)) CoreStore.defaultStack = stack do { try stack.addSQLiteStoreAndWait(fileName: "ConfigStore1.sqlite", configuration: "Config1", resetStoreOnModelMismatch: true) } ``` As for your module problems, did any of the suggestions I gave solved it for you? > I suggest you either: > 1. explicitly set the entity's Module field in your .xcdatamodeld to your main module's name (Don't leave it at Current Product Module) > 2. explicitly set the entity's Module field in your .xcdatamodeld to blank (Don't leave it at Current Product Module). Then set @objc (YourClass) on your NSManagedObject subclass. > 3. make sure that the Target Membership of your NSManagedObject subclass (.swift file) and your .xcdatamodeld include the same schemes. You probably need to remove them from the Unit Test scheme.
Author
Owner

@tmspzz commented on GitHub (Feb 10, 2016):

Thanks for the pointers but I am using an In Memory store so I should have to need to delete anything on disk. I am trying now to just created a DataStack and hold on to it instead of assigning it to the CoreStore.defaultStack. I'll se if this solves the issue.

About the previous question I went with option 3 which seems the most clean to me.

You have a truly great README for this project, I have rarely seen such extensive documentation. Although I believe that adding a few information about how to unit test will easy the adoption of your framework.

@tmspzz commented on GitHub (Feb 10, 2016): Thanks for the pointers but I am using an In Memory store so I should have to need to delete anything on disk. I am trying now to just created a DataStack and hold on to it instead of assigning it to the CoreStore.defaultStack. I'll se if this solves the issue. About the previous question I went with option 3 which seems the most clean to me. You have a truly great README for this project, I have rarely seen such extensive documentation. Although I believe that adding a few information about how to unit test will easy the adoption of your framework.
Author
Owner

@tmspzz commented on GitHub (Feb 10, 2016):

So in Unit Tests creating a DataStack with InMemoryStore and holding on to it works, as opposed to using the defaultStack in the Singleton CoreStore.defaultStack = someStack

Using the Singleton generates the following error:

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Object's persistent store is not reachable from this NSManagedObjectContext's coordinator'
@tmspzz commented on GitHub (Feb 10, 2016): So in Unit Tests creating a `DataStack` with InMemoryStore and holding on to it works, as opposed to using the defaultStack in the Singleton `CoreStore.defaultStack = someStack` Using the Singleton generates the following error: ``` *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Object's persistent store is not reachable from this NSManagedObjectContext's coordinator' ```
Author
Owner

@JohnEstropia commented on GitHub (Feb 11, 2016):

Thanks for the feedback!

I am trying now to just created a DataStack and hold on to it instead of assigning it to the CoreStore.defaultStack.

This should be fine. Just make sure you create a new DataStack for each test and make sure you do fetches from the stack and not from CoreStore.

Using the Singleton generates the following error

I think that's because you're using methods from CoreStore instead of the DataStack. For example, instead of using

CoreStore.beginAsynchronous(...)

you should use

dataStack.beginAsynchronous(...)

The former calls beginAsynchronous() on the CoreStore.defaultStack, which you mentioned you are not assigning to.

@JohnEstropia commented on GitHub (Feb 11, 2016): Thanks for the feedback! > I am trying now to just created a DataStack and hold on to it instead of assigning it to the CoreStore.defaultStack. This should be fine. Just make sure you create a new DataStack for each test and make sure you do fetches from the stack and not from `CoreStore`. > Using the Singleton generates the following error I think that's because you're using methods from `CoreStore` instead of the `DataStack`. For example, instead of using ``` swift CoreStore.beginAsynchronous(...) ``` you should use ``` dataStack.beginAsynchronous(...) ``` The former calls `beginAsynchronous()` on the `CoreStore.defaultStack`, which you mentioned you are not assigning to.
Author
Owner

@tmspzz commented on GitHub (Feb 11, 2016):

Yes, that was the case, I was calling methods on CoreStore directly. I think that for now I don't have anymore issues. Thanks for the help.

@tmspzz commented on GitHub (Feb 11, 2016): Yes, that was the case, I was calling methods on CoreStore directly. I think that for now I don't have anymore issues. Thanks for the help.
Author
Owner

@JohnEstropia commented on GitHub (Feb 12, 2016):

@blender I created a separate issue to address common mistakes here
https://github.com/JohnEstropia/CoreStore/issues/48
Instead of including in the README, I'll try to catch these mistakes from within CoreStore and log suggestions how to fix them.

@JohnEstropia commented on GitHub (Feb 12, 2016): @blender I created a separate issue to address common mistakes here https://github.com/JohnEstropia/CoreStore/issues/48 Instead of including in the README, I'll try to catch these mistakes from within CoreStore and log suggestions how to fix them.
Author
Owner

@winni2k commented on GitHub (Jan 8, 2017):

This was super helpful. I have been banging my head against a wall for a day on this.

I wish this issue was linked from the main README.md of this project.

@winni2k commented on GitHub (Jan 8, 2017): This was super helpful. I have been banging my head against a wall for a day on this. I wish this issue was linked from the main `README.md` of this project.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/CoreStore#39