Mocking objects marked as final for testing: Abort trap 6

Sample project that shows the issue: https://github.com/skladek/RealmObjectMocking


I'm running into an issue with adding a protocol to an external dependency for testing. I'm receiving an Abort Trap 6 error when building my test target. Everything I've searched around this says that turning off optimization will solve it, but that hasn't worked for me. Here's my setup and what I'm trying to accomplish:


The external dependency is Realm, but I don't think the issue is related to it specifically. I'm trying to make a protocol that mimics the public interface of the main Realm object. This will let me write tests to ensure that my objects are calling the correct methods on the Realm object. Here's what I've added:


RealmProtocol.swift: The protocol that mimics the Realm interface.


import RealmSwift

protocol RealmProtocol {
    func add(_ object: Object, update: Bool)
    func beginWrite()
    func commitWrite(withoutNotifying tokens: [NotificationToken]) throws
    func object<Element: Object, KeyType>(ofType type: Element.Type, forPrimaryKey key: KeyType) -> Element?
    func objects<Element: Object>(_ type: Element.Type) -> Results<Element>
}


Realm.swift: An extension to add RealmProtocol adherence to the Realm object. The Realm object is marked as final, adding the protocol as an extension seems to be my only option here. I can't subclass the Realm object to add it that way. If I can get the Realm object to adhere to the RealmProtocol, I can pass it around as RealmProtocol everywhere in my app and pass in a MockRealm object in my tests.


import RealmSwift

extension Realm: RealmProtocol {}


MockRealm.swift: My mock Realm object. This is only used in the test target. It will let me set flags to check that the correct method is called at the correct time.


import Foundation
import RealmSwift

@testable import RealmObjectMocking

class MockRealm: RealmProtocol {
    var addCalled = false
    var beginWriteCalled = false
    var commitWriteCalled = false
    var objectCalled = false
    var objectsCalled = false

    var objectReturn: Any?

    func add(_ object: Object, update: Bool) {
        addCalled = true
    }

    func beginWrite() {
        beginWriteCalled = true
    }

    func commitWrite(withoutNotifying tokens: [NotificationToken]) throws {
        commitWriteCalled = true
    }

    func object<Element: Object, KeyType>(ofType type: Element.Type, forPrimaryKey key: KeyType) -> Element? {
        objectCalled = true

        let object = objectReturn as? Element

        return object
    }

    func objects<Element: Object>(_ type: Element.Type) -> Results<Element> {
        objectsCalled = true

        let realm = try! Realm()
        return realm.objects(type)
    }
}


What's happening: Everything works fine until I add the Realm.swift extension. At that point, I get the abort trap 6 when attempting to build my tests. If I remove that, my tests build, but I can't pass the Realm object around as RealmProtocol and I can't swap it out for MockRealm in my tests.


I'm not sure if this is a circular dependency issue or something else. I have to import RealmSwift in all 3 of those files. Possibly the compiler not liking something there?


It also seems strange that I can add an extension to a final class. Is that intended? Seems like an easy way to sidestep final.


Does anyone have a thought on what could be causing this or how I can get past the warning? Currently my solution is to make a RealmWrapper object that simply passes calls into an actual Realm object, but that's a shim where I don't think there needs to be one.


I have also filed a Radar for this since it crashes the editor in Xcode when opening MockRealm.swift if the Realm extension is in the project.

Replies

This appears to be an Xcode bug rather than an implementation issue. This is fixed in Xcode10-Beta 1. Since my product will launch after Xcode 10 and iOS 12 are released, I'm content with leaving as is.