1

My app uses CoreData. One of the entities is

final class ViewItem: NSManagedObject {
// …
}  

During unit tests, I am trying to execute the following fetch request:

let viewItemFetchRequest = NSFetchRequest<ViewItem>(entityName: "ViewItem")  

as

viewContext.performAndWait {
  do {
    let fetchedViewItems = try viewContext.fetch(viewItemsFetchRequest)
    let viewItems = Set(fetchedViewItems)
    // …
  }  

Although the code executes correctly during normal operation, during unit tests it crashes.

When I set a breakpoint behind the try instruction, this breakpoint is reached, i.e. the fetch does not throw.
However I get a fatal error when I want to access fetchedViewItems, either by Set(fetchedViewItems) or with

(lldb) po fetchedViewItems
Fatal error: NSArray element failed to match the Swift Array Element type
Expected ViewItem but found ViewItem
2022-06-07 10:31:30.245019+0200 ShopEasy[42069:1864673] Fatal error: NSArray element failed to match the Swift Array Element type
Expected ViewItem but found ViewItem
1 value  

Here is the relevant part of the stack frame:
enter image description here

What could be the reason and how could I fix the problem?

PS: I found some posts related to this error, this, this and this one, but in none of them is reported Expected <some type> but found <some type>.

EDIT:

To investigate the situation, I modified the code as follows:

viewContext.performAndWait {
  do {
    let fetchedViewItems: [ViewItem] = try viewContext.fetch(viewItemsFetchRequest)
    let fetchedObjects: [AnyObject] = try viewContext.fetch(viewItemsFetchRequest)
    let viewItems = Set(fetchedViewItems)
    // …
  }  

and set a breakpoint at let viewItems = Set(fetchedViewItems), the instruction that crashes.
This is the situation when the breakpoint is reached:

Printing the description of fetchedObjects in the Variables View of the Debug area gives

Printing description of fetchedObjects:
▿ 1 element
  ▿ 0 : <ShopEasy.ViewItem: 0x10b3147c0> (entity: ViewItem; id: 0x10b822540 <x-coredata:///ViewItem/t51E2E772-5F7C-43E4-BBC6-68DC139F1BE02>; data: {
    fixedAtTopAt = nil;
…

and in the console

(lldb) po fetchedObjects
▿ 1 element
  ▿ 0 : <ShopEasy.ViewItem: 0x10b3147c0> (entity: ViewItem; id: 0x10b822540 <x-coredata:///ViewItem/t51E2E772-5F7C-43E4-BBC6-68DC139F1BE02>; data: {
    fixedAtTopAt = nil;
…

However, printing the description of fetchedViewItems in the Variables View of the Debug area gives

Printing description of fetchedViewItems:
([ShopEasyTests.ViewItem]) fetchedViewItems = 1 value {
  [0] = 0x000000010b3147c0
}

but in the console

(lldb) po fetchedViewItems
Fatal error: NSArray element failed to match the Swift Array Element type
Expected ViewItem but found ViewItem
2022-06-08 08:54:02.359228+0200 ShopEasy[51124:2241262] Fatal error: NSArray element failed to match the Swift Array Element type
Expected ViewItem but found ViewItem
1 value

This seems to be some conflict between the apps namespace (ShopEasy.ViewItem) and the unit tests namespace (ShopEasyTests.ViewItem).

Reinhard Männer
  • 14,022
  • 5
  • 54
  • 116
  • Am I understanding this correctly: the same code is fine when running your app, but fails when running as a unit test? How are you setting up your persistent store for unit tests? What method of managed object subclass definition are you using (you have files in your project, you let Xcode do that derived data thing...?) – jrturton Jun 07 '22 at 09:39
  • Yes, the code only fails during a unit test. During normal operation, my persistent stores have type `NSSQLiteStoreType`, but during unit tests they have `NSInMemoryStoreType`. The managed object subclass definition is done using files, i.e. in the core data model, Codegen is set to Manual/None. – Reinhard Männer Jun 07 '22 at 10:08
  • 1
    Not sure it is relevant but today it is more common to use SQLite also for tests but point the url for the store to /dev/null, something like `self.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")` instead of using `NSInMemoryStoreType` – Joakim Danielson Jun 07 '22 at 11:36
  • @JoakimDanielson Interesting! And this creates an SQL store in memory? – Reinhard Männer Jun 07 '22 at 12:16
  • Yes it does, it's a good solution that also Apples uses in their demo code. – Joakim Danielson Jun 07 '22 at 12:40
  • Joakim's point is a great one, I would definitely set up your unit tests using the dev/null approach. I don't know what your specific problem is but having the tests behave as closely as possible to the app code is always a good idea. – jrturton Jun 07 '22 at 13:02
  • I tried the unit test with `NSSQLiteStoreType` and `URL(fileURLWithPath: "/dev/null")`but the error remains the same. – Reinhard Männer Jun 07 '22 at 16:03
  • @JoakimDanielson I am trying to use your suggestion with URL(fileURLWithPath: "/dev/null") in my production code, but I have 3 persistent stores, and if I assigne all of them the same URL, I get the error message "Can't add the same store twice". Do you have any idea how to solve this? – Reinhard Männer Jun 09 '22 at 15:06

2 Answers2

4

I could reproduce your error in a sample project where the managed object class definition file was added both to the application target and the unit tests target. This also gave some console warnings when running tests about the class being implemented in two places.

Is this what is happening in your case?

enter image description here

The code should only be present in your app target, and you should use @testable import ShopEasy to access app code in your unit tests.

jrturton
  • 118,105
  • 32
  • 252
  • 268
0

I had the same error message, in my case I was missing:

@objc(ViewItem) on top of the NSManagedObject class definition.

I only had:

final class ViewItem: NSManagedObject {

instead of:

@objc(ViewItem)
final class ViewItem: NSManagedObject {
Lord Zsolt
  • 6,492
  • 9
  • 46
  • 76