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

2

8

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 ?

Hydrostatic answered 15/5, 2015 at 12:21 Comment(5)
Have you marked your methods and properties as public in your Airport class? Swift access control may be the issueRobinetta
Just tried, same result :(Hydrostatic
I ended up creating entities manually without MR shorthand for insertion...Empedocles
With Core Data's methods insertNewObjectForEntityForName etc ?Hydrostatic
@Hydrostatic i posted the solution. Please file a bug report with Apple as well and let me know if I can assist furtherEmpedocles
E
3

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

Empedocles answered 27/5, 2015 at 18:9 Comment(0)
R
0

There are two ways to make your Swift application classes available to the test target:

  1. Make your application classes public – this includes every variable, constant, and function you want to test

  2. Add your application Swift file to your Test target. This is pretty simple to do.

Rahn answered 27/5, 2015 at 7:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.