How to test equality of Swift enums with associated values

Swift

Swift Problem Overview


I want to test the equality of two Swift enum values. For example:

enum SimpleToken {
    case Name(String)
    case Number(Int)
}
let t1 = SimpleToken.Number(123)
let t2 = SimpleToken.Number(123)
XCTAssert(t1 == t2)

However, the compiler won't compile the equality expression:

error: could not find an overload for '==' that accepts the supplied arguments
    XCTAssert(t1 == t2)
    ^~~~~~~~~~~~~~~~~~~

Do I have do define my own overload of the equality operator? I was hoping the Swift compiler would handle it automatically, much like Scala and Ocaml do.

Swift Solutions


Solution 1 - Swift

Swift 4.1+

As @jedwidz has helpfully pointed out, from Swift 4.1 (due to SE-0185, Swift also supports synthesizing Equatable and Hashable for enums with associated values.

So if you're on Swift 4.1 or newer, the following will automatically synthesize the necessary methods such that XCTAssert(t1 == t2) works. The key is to add the Equatable protocol to your enum.

enum SimpleToken: Equatable {
    case Name(String)
    case Number(Int)
}
let t1 = SimpleToken.Number(123)
let t2 = SimpleToken.Number(123)

Before Swift 4.1

As others have noted, Swift doesn't synthesize the necessary equality operators automatically. Let me propose a cleaner (IMHO) implementation, though:

enum SimpleToken: Equatable {
    case Name(String)
    case Number(Int)
}

public func ==(lhs: SimpleToken, rhs: SimpleToken) -> Bool {
    switch (lhs, rhs) {
    case let (.Name(a),   .Name(b)),
         let (.Number(a), .Number(b)):
      return a == b
    default:
      return false
    }
}

It's far from ideal — there's a lot of repetition — but at least you don't need to do nested switches with if-statements inside.

Solution 2 - Swift

Implementing Equatable is an overkill IMHO. Imagine you have complicated and large enum with many cases and many different parameters. These parameters will all have to have Equatable implemented, too. Furthermore, who said you compare enum cases on all-or-nothing basis? How about if you are testing value and have stubbed only one particular enum parameter? I would strongly suggest simple approach, like:

if case .NotRecognized = error {
    // Success
} else {
    XCTFail("wrong error")
}

... or in case of parameter evaluation:

if case .Unauthorized401(_, let response, _) = networkError {
    XCTAssertEqual(response.statusCode, 401)
} else {
    XCTFail("Unauthorized401 was expected")
}

Find more elaborate description here: https://mdcdeveloper.wordpress.com/2016/12/16/unit-testing-swift-enums/

Solution 3 - Swift

enum MyEnum {
	case none
	case simple(text: String)
	case advanced(x: Int, y: Int)
}

func ==(lhs: MyEnum, rhs: MyEnum) -> Bool {
	switch (lhs, rhs) {
	case (.none, .none):
		return true
	case let (.simple(v0), .simple(v1)):
		return v0 == v1
	case let (.advanced(x0, y0), .advanced(x1, y1)):
		return x0 == x1 && y0 == y1
	default:
		return false
	}
}

Solution 4 - Swift

There seems no compiler generated equality operator for enums, nor for structs.

> “If you create your own class or structure to represent a complex data model, for example, then the meaning of “equal to” for that class or structure is not something that Swift can guess for you.” [1]

To implement equality comparison, one would write something like:

@infix func ==(a:SimpleToken, b:SimpleToken) -> Bool {
    switch(a) {
    
    case let .Name(sa):
        switch(b) {
        case let .Name(sb): return sa == sb
        default: return false
        }
    
    case let .Number(na):
        switch(b) {
        case let .Number(nb): return na == nb
        default: return false
        }
    }
}

[1] See "Equivalence Operators" at https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/AdvancedOperators.html#//apple_ref/doc/uid/TP40014097-CH27-XID_43

Solution 5 - Swift

Here's another option. It's mainly the same as the others except it avoids the nested switch statements by using the if case syntax. I think this makes it slightly more readable(/bearable) and has the advantage of avoiding the default case altogether.

enum SimpleToken: Equatable {
	case Name(String)
	case Number(Int)
}
extension SimpleToken {
	func isEqual(st: SimpleToken)->Bool {
		switch self {
		case .Name(let v1): 
            if case .Name(let v2) = st where v1 == v2 { return true }
		case .Number(let i1): 
            if case .Number(let i2) = st where i1 == i2 { return true }
		}
		return false
	}
}

func ==(lhs: SimpleToken, rhs: SimpleToken)->Bool {
	return lhs.isEqual(rhs)
}

let t1 = SimpleToken.Number(1)
let t2 = SimpleToken.Number(2)
let t3 = SimpleToken.Name("a")
let t4 = SimpleToken.Name("b")

t1 == t1  // true
t1 == t2  // false
t3 == t3  // true
t3 == t4  // false
t1 == t3  // false

Solution 6 - Swift

I'm using this simple workaround in unit test code:

extension SimpleToken: Equatable {}
func ==(lhs: SimpleToken, rhs: SimpleToken) -> Bool {
    return String(stringInterpolationSegment: lhs) == String(stringInterpolationSegment: rhs)
}

It uses string interpolation to perform the comparison. I would not recommend it for production code, but it's concise and does the job for unit testing.

Solution 7 - Swift

Another option would be to compare the string representations of the cases:

XCTAssert(String(t1) == String(t2))

For example:

let t1 = SimpleToken.Number(123) // the string representation is "Number(123)"
let t2 = SimpleToken.Number(123)
let t3 = SimpleToken.Name("bob") // the string representation is "Name(\"bob\")"

String(t1) == String(t2) //true
String(t1) == String(t3) //false

Solution 8 - Swift

Expanding on mbpro's answer, here's how I used that approach to check for equality of swift enums with associated values with some edge cases.

Of course you can do a switch statement, but sometimes it's nice to just check for one value in one line. You can do it like so:

// NOTE: there's only 1 equal (`=`) sign! Not the 2 (`==`) that you're used to for the equality operator
// 2nd NOTE: Your variable must come 2nd in the clause

if case .yourEnumCase(associatedValueIfNeeded) = yourEnumVariable {
  // success
}

If you want to compare 2 conditions in the same if clause, you need to use the comma instead of the && operator:

if someOtherCondition, case .yourEnumCase = yourEnumVariable {
  // success
}

Solution 9 - Swift

Another approach using if case with commas, which works in Swift 3:

enum {
  case kindOne(String)
  case kindTwo(NSManagedObjectID)
  case kindThree(Int)

  static func ==(lhs: MyEnumType, rhs: MyEnumType) -> Bool {
    if case .kindOne(let l) = lhs,
        case .kindOne(let r) = rhs {
        return l == r
    }
    if case .kindTwo(let l) = lhs,
        case .kindTwo(let r) = rhs {
        return l == r
    }
    if case .kindThree(let l) = lhs,
        case .kindThree(let r) = rhs {
        return l == r
    }
    return false
  }
}

This is how I wrote in my project. But I can't remember where I got the idea. (I googled just now but didn't see such usage.) Any comment would be appreciated.

Solution 10 - Swift

t1 and t2 are not numbers, they are instances of SimpleTokens with values associated.

You can say

var t1 = SimpleToken.Number(123)

You can then say

t1 = SimpleToken.Name(“Smith”) 

without a compiler error.

To retrieve the value from t1, use a switch statement:

switch t1 {
    case let .Number(numValue):
        println("Number: \(numValue)")
    case let .Name(strValue):
        println("Name: \(strValue)")
}

Solution 11 - Swift

the 'advantage' when compare to accepted answer is, that there is no 'default' case in 'main' switch statement, so if you extend your enum with other cases, the compiler will force you to update the rest of code.

enum SimpleToken: Equatable {
    case Name(String)
    case Number(Int)
}
extension SimpleToken {
    func isEqual(st: SimpleToken)->Bool {
        switch self {
        case .Name(let v1):
            switch st {
            case .Name(let v2): return v1 == v2
            default: return false
            }
        case .Number(let i1):
            switch st {
            case .Number(let i2): return i1 == i2
            default: return false
            }
        }
    }
}


func ==(lhs: SimpleToken, rhs: SimpleToken)->Bool {
    return lhs.isEqual(rhs)
}

let t1 = SimpleToken.Number(1)
let t2 = SimpleToken.Number(2)
let t3 = SimpleToken.Name("a")
let t4 = SimpleToken.Name("b")

t1 == t1  // true
t1 == t2  // false
t3 == t3  // true
t3 == t4  // false
t1 == t3  // false

Solution 12 - Swift

From Swift 4.1 just add Equatable protocol to your enum and use XCTAssert or XCTAssertEqual:

enum SimpleToken : Equatable {
	case Name(String)
	case Number(Int)
}
let t1 = SimpleToken.Number(123)
let t2 = SimpleToken.Number(123)
XCTAssertEqual(t1, t2) // OK

Solution 13 - Swift

You can compare using switch

enum SimpleToken {
    case Name(String)
    case Number(Int)
}

let t1 = SimpleToken.Number(123)
let t2 = SimpleToken.Number(123)

switch(t1) {
    
case let .Number(a):
    switch(t2) {
        case let . Number(b):
            if a == b
            {
                println("Equal")
        }
        default:
            println("Not equal")
    }
default:
    println("No Match")
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionJay LieskeView Question on Stackoverflow
Solution 1 - SwiftradexView Answer on Stackoverflow
Solution 2 - SwiftmbproView Answer on Stackoverflow
Solution 3 - SwiftneoneyeView Answer on Stackoverflow
Solution 4 - SwiftpaivView Answer on Stackoverflow
Solution 5 - SwiftDaniel WoodView Answer on Stackoverflow
Solution 6 - SwiftNikolai RuheView Answer on Stackoverflow
Solution 7 - SwiftDanielView Answer on Stackoverflow
Solution 8 - SwiftteradylView Answer on Stackoverflow
Solution 9 - SwiftLShiView Answer on Stackoverflow
Solution 10 - SwiftCarolineView Answer on Stackoverflow
Solution 11 - Swiftuser3441734View Answer on Stackoverflow
Solution 12 - SwiftiUriiView Answer on Stackoverflow
Solution 13 - SwiftRachitView Answer on Stackoverflow