Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

swift - Unit test CoreData (+ MagicalRecord) model triggers EXC_BAD_ACCESS

I need to unit test (XCTest) some of my methods that include reference to CoreData models.

The following line execute correctly :

var airport: AnyObject! = Airport.MR_createEntity()

(lldb) po airport <Airport: 0x7fcf54216940> (entity: Airport; id: 0x7fcf54216a20 <x-coredata:///Airport/t1D3D08DA-70F9-4DA0-9487-BD6047EE93692> ; data: {
    open = nil;
    shortName = nil;
    visible = nil; })

whereas the following line triggers an EXC_BAD_ACCESS :

var airport2: Airport = Airport.MR_createEntity() as! Airport

(lldb) po airport2
error: Execution was interrupted, reason: EXC_BAD_ACCESS (code=1, address=0x0).
The process has been returned to the state before expression evaluation.

No sign of this error with my principal target. The configuration is : model objects in both targets, class prefixed by @objc(MyModel), no namespace in class' models in my xcdatamodel

Any idea what's going on here ?

like image 526
Yaman Avatar asked May 15 '15 12:05

Yaman


1 Answers

Right, so I have finally gotten to the bottom of this and its not pretty. There is actually a radar for this issue as it appears to be a bug with the Swift compiler not recognizing ManagedObject casting in test targets. So add your voice to the noise

Starting off with an entity defined as follows:

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

I wrote a simple test class in which I create a MO in 3 different ways:

The first two failed:

let context = NSManagedObjectContext.MR_defaultContext()

func testMagicalRecordCreation() {
    let m = Member.MR_createInContext(context) as? Member
    XCTAssertNotNil(m, "Failed to create object")//fails
}

func testEntityDescriptionClassCreation() {
    let m2 = NSEntityDescription.insertNewObjectForEntityForName("Member", inManagedObjectContext: context) as? Member
    XCTAssertNotNil(m2, "Failed to create object")//fails
}

And then a success

func testManualMOCreation() {
    let ent = NSEntityDescription.entityForName("Member", inManagedObjectContext: context)!
    let m3 = Member(entity: ent, insertIntoManagedObjectContext: context)
    XCTAssertNotNil(m3, "Failed to create object")
}

This means that right now you have two options. Write your tests in Objective-C; or create a utility method to insert test objects into a context using the means I showed above.

Theres a nice post about this behaviour here

I ended up using an NSManagedObjectContext extension to be used explicitly in Swift Tests:

extension NSManagedObjectContext {
    func insertTestEntity<T: NSManagedObject>(entity: T.Type) -> T {
        let entityDescription = NSEntityDescription.entityForName(NSStringFromClass(T.self), inManagedObjectContext: self)!
        return T(entity: entityDescription, insertIntoManagedObjectContext: self)
    }
}

And it could be used like this:

func testConvenienceCreation() {
    let m4 = context.insertTestEntity(Member)
    XCTAssertNotNil(m4, "Failed to create object")
}

More reading about this kind of approach here

like image 133
Daniel Galasko Avatar answered Nov 07 '22 19:11

Daniel Galasko