Hallo keizerinnen en keizers, Leo hier. The topic today is various ways to do equatable in Unit Tests using Swift.

Today we will explore a somewhat common problem when testing code in Swift/iOS development. I can’t stress enough the fact we all should be doing tests, this is a real game changer for any developer.

A long time ago I’ve watched a lecture about code standards and the speaker said something that echoes in me until today:

“What reasoning does someone does to write a line of code and think: “Well I don’t have to test this line.” When you put this into words, sounds weird isn’t it?”

I really hope that all my audience make tests for their code, this leads inevitably to comparisons. Either you use XCTAssertFalse, XCTAssertNil or any other assertion method eventually you will use some kind of equality. This post is about test equality and how you can compare two things in tests. So be prepared for some nontrivial syntax and various ways to solve the same problem.

Let’s test! But first…

 

This Black Friday, unlock over 40 hours of expert training, mentorship, and community support to secure your place among the best devs. Click for early access to this limited offer!

Painting of The Day

This is a 1664 masterpiece called Crimean Falconer of King John Casimir from the genius Daniel Schultz. Born somewhere around 1615 in Gdańsk (Danzig) in Pomeranian Voivodeship, Polish–Lithuanian Commonwealth, Schultz learned the art of painting from his uncle, Daniel Schultz the Elder, another vital painter, working in his workshop for about five years. After his uncle’s death, he traveled to France and the Netherlands to continue his studies for about three years. The inspirations of Rembrandt and Philippe de Champaigne are visible in his works.

I choose this painting because if you pay attention to the most right kid and the king in the middle, they have a very very similar faces, but they are not equal… are they? As we will talk about equality, an old painting with a family is an amazing example to acknowledge that not everything that looks the same, is the same.

 

The problem – Best ways to do Equatable in Unit Tests using Swift

You have a complex type that you want to check its equality with another object in the test suite.

Imagine you have this code within your codebase:

protocol SimplePrint {
    func printAll()
}

struct SimpleManager: SimplePrint {
    func printAll() {
        print("All")
    }
}

struct ComplexType {
    let name: String
    let action: SimplePrint
}


let complex1 = ComplexType(name: "First", action: SimpleManager())
let complex2 = ComplexType(name: "Second", action: SimpleManager()) 

What happens if we have to check in the test suit if complex1 is equal complex2?

Best ways to do Equatable in Unit Tests using Swift guide error

Well, the answer is you can’t do that because the compiler can’t infer how to compare those two objects, so we need to help him. Let’s start with the most straightforward Xcode recommendation strategy: the equatable protocol.

 

Solution 1 – Conforming to Equatable

First, you should import XCTest and do a little setup to be able to test. Open Xcode Playground if you want to code along, and I recommend that. And let’s dive into the problem:

class MyTestCase: XCTestCase {
    
    let complex1 = ComplexType(name: "First", action: SimpleManager())
    let complex2 = ComplexType(name: "Second", action: SimpleManager())
    
    func testMyCaseTestsNow() {
        XCTAssertEqual(complex1, complex1)
    }
}

MyTestCase.defaultTestSuite.run()

You get **Global function XCTAssertEqual(_:_:_:file:line:) requires that ComplexType conform to Equatable**.

So Swift recommends that you use equatable. Let’s conform our ComplexType to Equatable.

struct ComplexType: Equatable {
    
    let name: String
    let action: SimplePrint
    
    static func == (lhs: ComplexType, rhs: ComplexType) -> Bool {
        return lhs.name == rhs.name
    }
}

Now you can use XCTAssertEqual function on the object. But this could be improved, right?

 

Using Identifiable

Ideally, we should only assert equality on objects that we can uniquely identify or all the properties are not relevant to equality (like money, a 5 dollar bill is equal other 5 dollar bill even being different bills). Today is our lucky day! Swift has its own protocol that enforces this, let’s use it:

struct ComplexType: Equatable, Identifiable {
    var id: String
    let name: String
    let action: SimplePrint
    
    static func == (lhs: ComplexType, rhs: ComplexType) -> Bool {
        return lhs.id == rhs.id
    }
}

Now when we compare two ComplexType structs we are sure that they are the same. Calling the tests again:

func testMyCaseTestsNow() {
    let complex1 = ComplexType(id: UUID().uuidString, name: "First", action: SimpleManager())
    let complex2 = ComplexType(id: UUID().uuidString, name: "Second", action: SimpleManager())
    
    XCTAssertEqual(complex1, complex1)
}

And this was the first solution!

 

This Black Friday, unlock over 40 hours of expert training, mentorship, and community support to secure your place among the best devs. Click for early access to this limited offer!

Solution 2 – Turn All Properties Into Equatable

Now things begin to be interesting. Now our ComplexType has three properties: Two Strings ( they are already equatable) and a SimplePrint type.

struct ComplexType: Equatable, Identifiable {
    var id: String
    let name: String
    let action: SimplePrint
    
    static func == (lhs: ComplexType, rhs: ComplexType) -> Bool {
        return lhs.id == rhs.id
    }
}

But how can we use equatable inference for ComplexType? We need that SimplePrint protocol to be equatable too. Like this code below right….? :

let action: SimplePrint & Equatable

Not exactly, because the `Equatable` protocol has Self requirements. And the code below shows how Generics come to save the day again:

struct ComplexType<ActionType: SimplePrint & Equatable>: Identifiable {
    var id: String
    let name: String
    let action: ActionType
}

But now we have another problem. SimpleManager is not Equatable. This will force us two choose… If we can turn SimpleManager a Equatable struct is solved, but if not we can also use conditional protocol conformance, like the example below:

struct ComplexType<ActionType: SimplePrint>: Identifiable {
    var id: String
    let name: String
    let action: ActionType
}

extension ComplexType: Equatable where ActionType: Equatable {} 

This way our ComplexType can be equatable or not depending on what conforms to ActionType. Really amazing, isn’t it?

Now we have to change the tests and create some managers that conform to Equatable and SimplePrint:

struct SimpleComparableManager: SimplePrint, Equatable, Identifiable {
    var id: String
    
    func printAll() {
        print("All")
    }
}

class MyTestCase: XCTestCase {
    
    func testMyCaseTestsNow() {
        let complex1 = ComplexType(id: UUID().uuidString, name: "First", action: SimpleComparableManager(id: "Manager 1"))
        //let complex2 = ComplexType(id: UUID().uuidString, name: "Second", action: SimpleManager())
        
        XCTAssertEqual(complex1, complex1)
    }
}

That was our second possible alternative to solve the unit test equality problem!

 

Solution 3 – Use Enums

So if you are sure that you have limited and well-defined implementations of the SimplePrint protocol, you can substitute that type by an enum in the ComplexType, check the enum below:

enum PrintManager: Equatable {
    case simple(SimpleManager)
    case comparable(SimpleComparableManager)
    
    static func == (lhs: PrintManager, rhs: PrintManager) -> Bool {
        switch (lhs, rhs) {
        case (.simple(_), .simple(_)):
            return true
        case let (.comparable(a), .comparable(b)):
            return a == b
        case (.simple(_), _):
            return false
        case (.comparable(_),_):
            return false
        }
    }
}

This way you can still force the enum itself to implement the Equatable protocol to give us freedom for our types to not implement at all! Now fixing the ComplexType and the tests:

struct ComplexType: Identifiable, Equatable {
    var id: String
    let name: String
    let action: PrintManager
}

class MyTestCase: XCTestCase {
    
    func testMyCaseTestsNow() {
        let complex1 = ComplexType(id: UUID().uuidString, name: "First", action: .comparable(SimpleComparableManager(id: "123123")))
        //let complex2 = ComplexType(id: UUID().uuidString, name: "Second", action: SimpleManager())
        
        XCTAssertEqual(complex1, complex1)
    }
}

And… Voilà! You have your comparison and the flexibility to your managers not being necessarily equatable. By the way, this is the only way to compare two things that are not Equatable, you have to wrap your not Equatable Type into Equatable one… Or maybe there are other ways!

 

Solution 4 – Using XCTAssertIdentical and XCTAssertNotIdentical

For this, you will need to transform your structs into classes. This way you leave behind the value type of your object and embrace the reference type. Enabling us to compare not only values but also memory addresses.

Let’s do the code changes:

class ComplexType: Identifiable { // now is a class
    var id: String
    let name: String
    let action: SimplePrint
    
    init(id: String, name: String, action: SimplePrint) {
        self.id = id
        self.name = name
        self.action = action
    }
}

We have to add the initializer because we don’t receive a default one from classes. (Well, I know some classes you can create without init but this is a topic for another article).

Now we can compare their identities:

class MyTestCase: XCTestCase {
    
    func testMyCaseTestsNow() {
        let complex1 = ComplexType(id: UUID().uuidString, name: "First", action: SimpleComparableManager(id: "123123"))
        
        XCTAssertIdentical(complex1, complex1)
    }
}

And of course, we have the nonIdentical too:

class MyTestCase: XCTestCase {
    
    func testMyCaseTestsNow() {
        let complex1 = ComplexType(id: UUID().uuidString, name: "First", action: SimpleComparableManager(id: "123123"))
        let complex2 = ComplexType(id: UUID().uuidString, name: "First", action: SimpleComparableManager(id: "123123"))
        
        XCTAssertNotIdentical(complex1, complex2)
    }
}

**Important note: When comparing classes with XCTAssertIdentical or XCTAssertNotIdentical you are just testing if both of the variables point to the same object in the memory. You are not pointing to values here, keep this in mind!

 

Solution 5 – The Legendary Triple Equal Identity Operator

Another way to compare pointers in the memory (classes) is using the legendary triple equals identity operator ===:

class MyTestCase: XCTestCase {
    
    func testMyCaseTestsNow() {
        let complex1 = ComplexType(id: UUID().uuidString, name: "First", action: SimpleComparableManager(id: "123123"))
        
        XCTAssertTrue(complex1 === complex1)
    }
}

It’s the same result as using XCTAssertIdentical.

 

Solution 6 – Use ObjectIdentifier

Same as above but using the struct ObjectIdentifier to uniquely identify a class instance or meta type.

class MyTestCase: XCTestCase {
    
    func testMyCaseTestsNow() {
        let complex1 = ComplexType(id: UUID().uuidString, name: "First", action: SimpleComparableManager(id: "123123"))
        
        XCTAssertEqual(ObjectIdentifier(complex1), ObjectIdentifier(complex1))
    }
}

And we are done!

 

Summary – Best way to do Equatable in Unit Tests in Swift

Today we learn many ways to do object comparison in Swift. Using structs with generics with optional conformance protocol, or enums to wrap the non-equatable content or making it a class and comparing pointers in memory, the most important thing to keep in mind is what makes sense to your codebase. If using structs is fine, so let it be. Now we’ve explored many options for you and I’m sure there are many other ones!

That’s all my people, I hope you liked reading this article as much as I enjoyed writing it. If you want to support this blog you can Buy Me a Coffee or leave a comment saying hello. You can also sponsor posts and I’m open to freelance writing! You can reach me on LinkedIn or Twitter and send me an e-mail through the contact page.

Thanks for reading and… That’s all folks.

Credits:

title image