0

I want to implement a ValueObjectSharedExampleConfiguration: QuickConfiguration using Quick.

class ValueObjectSharedExampleConf: QuickConfiguration {
    override class func configure(_ configuration: Configuration) {
        sharedExamples("Value Object") {
            (context: @escaping SharedExampleContext) in
            describe("same objects") {
                it("should be equal") {
                    let obj1a = context()["1a"]
                    let obj1b = context()["1b"]
                    expect(obj1a == obj1b).to(beTrue())
                }
            }
            describe("different objects") {
                it("should not be equal") {
                    let obj1 = context()["1a"]
                    let obj2 = context()["2"]
                    expect(obj1 == obj2).to(beFalse())
                }
            }
        }
    }
}

And then I want to test any classes/structs that conforms to Equatable with this shared Example like this:

itBehavesLike("Value Object") { [ "obj1a": foo1a, "obj1b": foo1b, "obj2": foo2] }

But the problem is, SharedExampleContext is actually a closure returns [String: Any], so obj1a, obj1b, obj2 variables I get in sharedExample closure are all of type Any, which doesn't necessarily conform to Equatable. Thus the code obj1a == obj1b won't compile.

Actually if I check obj1a is Equatable it returns true. But I don't know how to cast it to a proper type that compiler will accept. obj1a as! Equatable won't compile because Equatable is a generic protocol.

I can't just write obj1a as! Foo because if there is another class Bar: Equatable I want my sharedExample also works for that.

The main problem here is: I have two variables cast to Any, which are guaranteed to be originally of same type that conforms to Equatable. How should I legally compare these two variables without knowledge of the actual type of them ?

Nandin Borjigin
  • 2,094
  • 1
  • 16
  • 37

2 Answers2

0

You could have a MyComparable protocol with a function isEqualTo(object: Any) and implement it for Foo and Bar. You can then:

let object1 = obj1 as! MyComparable
object1.isEqualTo(obj2)

and in the implementation of isEqualTo check the type or force convert it if you are sure it is always the same:

class Foo: MyComparable {
    func isEqualTo(_ object: Any) -> Bool {
        let obj2 = object as! Foo
        return self == obj2
    }
}
simonWasHere
  • 1,307
  • 11
  • 13
0

I found an strange solution.

func == (lhs: any Equatable, rhs: any Equatable) -> Bool {
    return ([lhs] as NSArray) == ([rhs] as NSArray)
}

Here is the code tested with swift 5.7.1

protocol GenericItem: Hashable {
    associatedtype Element
    var value: Element { get }
}

struct StringItem: GenericItem {
    var value: String
}

struct IntItem: GenericItem {
    var value: Int
}

// comparison function [A]
func == (lhs: any Equatable, rhs: any Equatable) -> Bool {
    return ([lhs] as NSArray) == ([rhs] as NSArray)
}

let item1: any GenericItem = StringItem(value: "hello")
let item2: any GenericItem = StringItem(value: "hello")
let item3: any GenericItem = StringItem(value: "world")
let item4: any GenericItem = IntItem(value: 24)

// without [A] it causes error as follows
// item1 == item2 // Binary operator '==' cannot be applied to two 'any GenericItem' operands
([item1] as NSArray) == ([item2] as NSArray) // true
([item2] as NSArray) == ([item3] as NSArray) // false
item1 == item2 // true
item2 == item3 // false
item3 == item4 // false

Since StringItem nor IntItem are not inherited from NSObject, I am not sure how NSArray or NSDictionary compares two instances of any Equatable. If we know how it works behind the scenes, we may come up with better implementation.

Note: Strange to say, if you change Hashable to Equatable, it behaves differently.

protocol GenericItem: Equatable { // Hashable -> Equatable
    associatedtype Element
    var value: Element { get }
}
// ...snip...
item1 == item2 // false <-- !?
item2 == item3 // false
item3 == item4 // false

It may not be a stable behavior. If you like to try, please test you code well before ship your products.

Kaz Yoshikawa
  • 1,577
  • 1
  • 18
  • 26