madcat
madcat

Reputation: 322

CoreData class miss match in unit test

When i try to retrieve an array of items from my model I always get an down cast error, because of a mismatch of the types of the classes. Swift has a strict namespace and the model items are different than the items I want to cat into. Here is my NSManagedObject:

import Foundation
import CoreData

@objc(Boss)
class Boss: NSManagedObject {    
    @NSManaged var name: String
}

The Testclass is following:

func testCheckIfFetchGetTheCorrectClass() {
   // setup item
   let entity = NSEntityDescription.entityForName("Boss", inManagedObjectContext: moc)
   let boss = Boss(entity: entity!, insertIntoManagedObjectContext: moc)

  boss.name = "Chef"

  var bosses = [Boss]()

  var request = NSFetchRequest(entityName: "Boss")
  var e: NSError?
  if let results = moc.executeFetchRequest(request, error: &e) {
    println("results: \n\(results.description)\nCount:\(results.count)")
    if let downcastedSwiftArray = results as? [Boss] {
      // downcastedSwiftArray contains only UIView objects
      bosses = downcastedSwiftArray
    } else  {
      XCTAssert(false, "Down Cast Error")
    }
    println("Bosses : \n\(bosses.description)")
  } else {
    println("fetch error: \(e!.localizedDescription)")
    abort();
  }

    // This is an example of a functional test case.
    XCTAssert(true, "Pass")
}

When I run the test following types will show up in the debugger:

bosses  [NameSpaceTestTests.Boss]   0 values        
results [AnyObject] 1 value 
  [0]   Boss_Boss_ *    

So it looks like the result array contains a Boss class item that will not match the bosses array.

How can I assign the result of the fetch request to my array?

You will find the complete project on github.

Upvotes: 2

Views: 2015

Answers (4)

ctietze
ctietze

Reputation: 2932

If you change

if let downcastedSwiftArray = results as? [Boss] {

to force downcast to

if let downcastedSwiftArray = results as! [Boss] {

does your test abort due to a runtime error?

I had problems downcasting to Core Data managed object types as well. Adding the NSManagedObject sublcasses' files to the test target made the code compile but didn't seem to work properly. So don't add code under test to the test target itself. Import it.

With Swift 2: Use @testable

Before Swift introduced the @testable import, we were forced to make classes we wanted to test public. That exposed far more of our code to client than we'd want. Nowadays, this is the way to go.

Before Swift 2: Make the class public

I solved this by making the classes public:

@objc(Boss)
public class Boss: NSManagedObject {    
    @NSManaged public var name: String
}

Then import the production module in tests:

import MyProjectTargetName

Upvotes: 4

Kevin R
Kevin R

Reputation: 8631

Making your classes public is a workaround and should not be needed for test dependencies.

The problem really is that the test target compiles his own version of the source classes, which isn't what you want. You'll end up with two versions of the same.

The following steps fixed it for me:

Remove all 'normal' classes from the test target compile list

enter image description here


Make sure the Module name for the app and test app are different (i.e. App and AppTests

enter image description here enter image description here


Make sure you import the app module in every test

enter image description here

Upvotes: 5

Ronnie Liew
Ronnie Liew

Reputation: 18270

One way to solve this is to dynamically alter the class name of the NSManagedObject subclass with something like:

    let managedObjectModel = NSManagedObjectModel.mergedModelFromBundles([NSBundle.mainBundle()])!

    // Check if it is within the test environment
    let environment = NSProcessInfo.processInfo().environment as! [String : AnyObject]
    let isTestEnvironment = (environment["XCInjectBundle"] as? String)?.pathExtension == "xctest"

    // Create the module name based on product name 
    let productName:String = NSBundle.mainBundle().infoDictionary?["CFBundleName"] as! String
    let moduleName = (isTestEnvironment) ? productName + "Tests" : productName

    let newManagedObjectModel:NSManagedObjectModel = managedObjectModel.copy() as! NSManagedObjectModel

    for entity in newManagedObjectModel.entities as! [NSEntityDescription] {
        entity.managedObjectClassName = "\(moduleName).\(entity.name!)"
    }

This helps to workaround the class name mismatch because when running the test, the class name is actually <Product Name>Tests.<Subclass Name> (in your case, it's NameSpaceTestTests.Boss)

Upvotes: 0

Mundi
Mundi

Reputation: 80273

In Swift you have to specify the Type of the array resulting from a fetch request. The if let pattern requires an optional cast.

if let result = moc.executeFetchRequest(request, error:&e) as? [Boss] {
    // use result
}

Upvotes: 0

Related Questions