Navigating Equatable in Swift for Effective Unit Testing?

abdul ahad
4 min readMay 28, 2024

--

Photo by Elena Kloppenburg on Unsplash

“Equatable” means that an object can be compared for equality with another object of the same type.

Equatable in Swift

In Swift, you make a type equatable by conforming it to the Equatable protocol. This protocol requires the implementation of the == operator.

Here’s an example of a simple struct conforming to Equatable:

struct Person: Equatable {
var name: String
var age: Int

// Automatically synthesizes the `==` operator
// if all properties are `Equatable`
}

// Alternatively, you can manually implement the `==` operator:
extension Person {
static func == (lhs: Person, rhs: Person) -> Bool {
return lhs.name == rhs.name && lhs.age == rhs.age
}
}

What is Equatable

In Swift, certain types are equatable by default, meaning that they automatically conform to the Equatable protocol without requiring any additional implementation. These types include:

Standard Library Types:

  • Numeric Types: Integer and floating-point types (Int, UInt, Double, Float, etc.).
  • Bool: The Boolean type (Bool) is equatable by default.
  • Character: The character type (Character) is equatable.
  • String: The string type (String) is equatable.
  • Optional: Optional types (Optional<T>, or its syntactic sugar T?) are equatable if the wrapped type T is equatable.
  • Array, Dictionary, Set: Collection types (Array, Dictionary, Set) are equatable if their elements are equatable.
  • Range, ClosedRange: Range types (Range, ClosedRange) are equatable if their bounds are equitable.
  • Tuple Types: Tuple types are equatable if their element types are equitable.
// Standard library types
let x: Int = 42
let y: Double = 3.14
let a: String = "Hello"
let b: [Int] = [1, 2, 3]

// Tuple types
let tuple1 = (1, "abc")
let tuple2 = (true, 42)

// Equatable check
print(x == 42) // true
print(a == "Hello") // true
print(b == [1, 2, 3]) // true

print(tuple1 == (1, "abc")) // true
print(tuple2 == (true, 42)) // true

In the above example, all the types used (Int, Double, String, [Int], and tuples) are equatable by default, so they can be compared using the equality operator (==) without any explicit conformance to the Equatable protocol.

Custom types that explicitly conform to Equatable

As shown in the Person struct example above, custom types can be made equatable by conforming to the Equatable protocol and implementing the == operator.

Types That Do Not Conform to Equatable by Default

Custom Classes without Equatable Conformance:

Classes do not automatically conform to Equatable. You must implement the == operator manually:

class Animal: Equatable {
static func == (lhs: Animal, rhs: Animal) -> Bool {
return lhs.name == rhs.name
}

var name: String
init(name: String) {
self.name = name
}
}

Equatable for Enums

in Swift, enums are automatically equatable by default if they do not have associated values. If an enum has associated values, it can still be equatable, but the compiler will only synthesize the == operator if all the associated values are themselves equatable.

Basic Enums

For simple enums without associated values, Swift automatically provides Equatable conformance:

enum Direction: String {
case east
case west
}

let direction1 = Direction.east
let direction2 = Direction.east
let direction3 = Direction.west

print(direction1 == direction2) // true
print(direction1 == direction3) // false

Enums with Associated Values

For enums with associated values, Swift will automatically synthesize the == operator if all associated values are equitable:

enum Result {
case success(message: String)
case failure(error: String)
}

// Since String is equatable, the Result enum gets synthesized `==` operator.
let result1 = Result.success(message: "Data loaded")
let result2 = Result.success(message: "Data loaded")
let result3 = Result.failure(error: "Network error")

print(result1 == result2) // true
print(result1 == result3) // false

If the associated values are not equatable, you need to manually implement the == operator:

struct CustomError {
let code: Int
let message: String
}

enum Result {
case success(message: String)
case failure(error: CustomError)
}

// implement the Equatable
extension Result: Equatable {
static func == (lhs: Result, rhs: Result) -> Bool {
switch (lhs, rhs) {
case (.success(let lhsMessage), .success(let rhsMessage)):
return lhsMessage == rhsMessage
case (.failure(let lhsError), .failure(let rhsError)):
return lhsError.code == rhsError.code && lhsError.message == rhsError.message
default:
return false
}
}
}

let customError1 = CustomError(code: 404, message: "Not found")
let customError2 = CustomError(code: 404, message: "Not found")
let customError3 = CustomError(code: 500, message: "Server error")

let result1 = Result.failure(error: customError1)
let result2 = Result.failure(error: customError2)
let result3 = Result.failure(error: customError3)

print(result1 == result2) // true
print(result1 == result3) // false

Equatable for Structs

Structs automatically conform to Equatable if all their properties are Equatable. Your Person struct demonstrates this:

Important the synthesized == operator for structs is only automatically generated if the struct explicitly conforms to the Equatable protocol. This means that simply having Equatable properties is not enough; the struct itself must declare conformance to Equatable.

// no compilation error in this case
struct Person: Equatable {
var name: String
var age: Int
}

Unit Testing with Equatable

In unit testing, you often need to compare objects to ensure they have the expected values. Conforming to Equatable simplifies this process, as it allows the use of XCTAssertEqual to compare objects directly.

In this example, the XCTAssertEqual leverage the Equatable conformance to compare the Person instances.

Conclusion

Understanding what is and isn’t equatable in Swift is essential for effective unit testing. Non-equatable types require more effort to compare, but by conforming to the Equatable protocol and ensuring all properties are also equatable, you can simplify comparisons and make your unit tests more robust.

--

--

abdul ahad
abdul ahad

Written by abdul ahad

A software developer dreaming to reach the top and also passionate about sports and language learning

No responses yet