How to test Core Data properly in Swift
Asked Answered
A

2

9

There are quite a few subjects on this already, but I have yet to find a solution that is workable for Swift (Xcode 6.2).

To test Core Data backed classes in Swift, I generate new Managed Object Contexts that I then inject into my classes.

//Given   
let testManagedObjectContext = CoreDataTestComposer.setUpInMemoryManagedObjectContext()
let testItems = createFixtureData(testManagedObjectContext) as [TestItem]
self.itemDateCoordinator.managedObjectContext = testManagedObjectContext

//When
let data = self.itemDateCoordinator.do()

//Then
XCTAssert(data.exists)

The issue comes from passing a MOC created in the Test to the class that's doing. Because entity classes are namespaced, Core Data won't fetch your the appropriate ManagedObject subclass and instead hands back a NSManagedObject set. When looping or doing anything with these objects (which in your class would be an array of test items ([TestItem]).

For example, the offending class ItemDateCoordinator would execute this loop (after pulling the relevant data from a NSFetchRequest)"

for testItem in testItems {
    testItem.doPart(numberOfDays: 10)
}

would result in:

fatal error: NSArray element failed to match the Swift Array Element type

Also, I have come across a collection of information without much of a solid answer:

  • To cast entities when creating them, I have been using a solution by Jesse, but that doesn't work on a larger scope of testing.
  • A solution has been posted on another question that involved swapping out the classes at runtime, but that hasn't worked for me with entity inheritance.
  • Is there another method to testing your objects with Core Data in this case? How do you do it?
Abdominous answered 14/4, 2015 at 0:25 Comment(1)
Are you facing a mocking problem?Anvers
B
13

I was about to point you toward Swift, Core Data, and unit testing but see you've already found it. :)

That post doesn't elaborate much on where your files should exist (i.e., in which Target). You should not add NSManagedObject subclasses (or any files really) to both targets. I've found that this leads to all kinds hard discover bugs and cryptic errors.

And definitely DO NOT do this. That is a terrible hack.

Instead, make your classes public and import MyAppTarget in your XCTestCase files. Better yet, your model should be in its own framework as I mention in my recent talk (a video will be posted in a few weeks on realm.io). Doing this makes your models namespace very clear and generally easier to deal with. Then you'll need to import MyAppModel everywhere you access your managed objects.

I also have a new framework, JSQCoreDataKit that intends to make Core Data easier to use in Swift. One key part of this framework is the CoreDataStack which you can initialize using an in-memory store for your tests. There's demo app with examples, and well-commented unit tests.

Blakeney answered 19/4, 2015 at 2:55 Comment(2)
Thank you, this was the answer that finally got me to a solution (Xcode 7, Swift 2). Add your NSManagedObject subclass files to the main target only, not to the tests target. I found it not necessary to make them public (I think this is an Xcode 7 change). At the top of my test files I added: @testable import MY_MODULE_NAME.Forensic
Solved by using @testable import ModuleName as described here; natashatherobot.com/swift-2-xcode-7-unit-testing-accessConsummate
I
4

I believe this was updated recently (iOS 9/Swift 2.0) to have the testable keyword on an imported target, mean that the target's internal classes (the default) become public. From the docs:

enter image description here

So to add to jessesquires answer above, append @testable to your import, and this should solve the unit test errors:

@testable import MyAppTarget
Iolenta answered 9/11, 2015 at 20:22 Comment(1)
+1 I have used your solution in a sample Swift 3.0 project (github.com/Daemon-Devarshi/MedicationSchedulerSwift3.0/tree/…), it works seamlessly :-)Saturnian

© 2022 - 2024 — McMap. All rights reserved.