227

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.

Jay Lieske
  • 4,788
  • 3
  • 30
  • 41
  • 1
    Opened rdar://17408414 (http://www.openradar.me/radar?id=6404186140835840). – Jay Lieske Jul 04 '14 at 22:59
  • 1
    From Swift 4.1 due to [SE-0185](https://github.com/apple/swift-evolution/blob/master/proposals/0185-synthesize-equatable-hashable.md), Swift also supports synthesizing `Equatable` and `Hashable` for enums with associated values. – jedwidz Nov 22 '18 at 12:54

15 Answers15

282

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.

fresskoma
  • 25,481
  • 10
  • 85
  • 128
radex
  • 6,336
  • 4
  • 30
  • 39
  • 40
    The thing that sucks about this is that you need to use the default statement in the switch, so if you add a new enum case, the compiler doesn't make sure you add the clause to compare that new case for equality – you'll just have to remember and be careful when you make changes later down the line! – Michael Waterfall May 28 '15 at 11:34
  • 21
    You could get rid of the problem @MichaelWaterfall mentioned by replacing `default` with `case (.Name, _): return false; case(.Number, _): return false`. – Kazmasaurus Aug 05 '15 at 23:30
  • 25
    Better: `case (.Name(let a), .Name(let b)) : return a == b` etc. – Martin R Nov 06 '15 at 22:29
  • 1
    With the where clause, won't each case continue to be tested until it hits default for every `false`? It might be trivial but that sort of thing can add up in certain systems. – Christopher Swasey Jan 20 '16 at 20:12
  • 1
    For this to work both `enum` and `==` function must be implemented on a global scope (outside the scope of your view controller). – Andrej May 29 '16 at 19:12
  • 1
    @Andrej 's comment is especially important, and in my case, a real drawback – J2N Sep 08 '16 at 18:32
  • 1
    Solution is not fit bets for the purpose. See my answer below. – mbpro Dec 18 '16 at 11:47
  • 1
    @Andrej you should only need to define the `==` function at the global scope; its parameters can refer to the nested enum classes. – Daniel Dickison Feb 10 '17 at 17:02
  • 1
    @radex For `enum E : Equatable { case a(Int), b(String), c, d }`, above approach solves `.a(1) == .a(1)`, etc., but *not* `.c == .d` and the like. Any suggestions? – t0rst Sep 25 '17 at 10:47
  • note if you don't want `default` could replace it with `case (.Name, .Number), (.Number, .Name)` (note also the `let`s don't seem to be necessary, at least not anymore). Although if you have a lot of cases it would be annoying to cover every possible combination. Same syntax also works for cases without associated types. – shim Mar 05 '20 at 16:52
  • The auto generated equatable method only works if the associated values themselves are equatable, just an FYI. I had closures as associated values and this means you still have to implement the `==` function yourself – Fonix Mar 26 '21 at 14:29
102

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/

Berik
  • 7,816
  • 2
  • 32
  • 40
mbpro
  • 2,460
  • 3
  • 22
  • 37
  • Could you give a more complete example when trying to use this not in a testing basis? – teradyl Aug 17 '17 at 00:33
  • 1
    I'm not sure what is the question here. `if case` and `guard case` are simply language constructs, you can use them anywhere when testing equality of enums in this case, not just in Unit Tests. – mbpro Mar 13 '18 at 09:21
  • 3
    While technically this answer does not answer the question, I suspect it actually makes many people arriving here via search, realise they were asking the wrong question to start with. Thanks! – Nikolay Suvandzhiev Jul 31 '18 at 15:21
27
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
    }
}
neoneye
  • 50,398
  • 25
  • 166
  • 151
  • This can also be written with something like `case (.Simple(let v0), .Simple(let v1))` Also the operator can be `static` inside the enum. See my answer here. – LShi Dec 08 '16 at 08:41
17

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

Cœur
  • 37,241
  • 25
  • 195
  • 267
paiv
  • 5,491
  • 22
  • 30
14

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
Daniel Wood
  • 4,487
  • 3
  • 38
  • 36
12

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.

Nikolai Ruhe
  • 81,520
  • 17
  • 180
  • 200
  • 2
    I agree, for unit testing this is a decent solution. – Daniel Wood Feb 04 '16 at 22:01
  • Apple docs on init(stringInterpolationSegment:) says: "Do not call this initializer directly. It is used by the compiler when interpreting string interpolations.". Just use `"\(lhs)" == "\(rhs)"`. – skagedal Jan 16 '17 at 02:26
  • 1
    You can also use `String(describing:...)` or the equivalent `"\(...)"`. But this does not work if the associated values differ :( – Martin Jul 04 '17 at 15:37
10

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
Daniel
  • 20,420
  • 10
  • 92
  • 149
10

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
}
teradyl
  • 2,584
  • 1
  • 25
  • 34
3

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.

LShi
  • 1,500
  • 16
  • 29
3

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
iUrii
  • 11,742
  • 1
  • 33
  • 48
2

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)")
}
Caroline
  • 4,875
  • 2
  • 31
  • 47
2

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
user3441734
  • 16,722
  • 2
  • 40
  • 59
0

Additionally to the answers above, you can add computed properties as helpers. It's one of the many ways to optimise readability also.

    enum UserAccountViewStack: Hashable {
        case notLoggedIn
        case initialDevicePairing
        case deviceMainView
        case leftMenu(LeftMenuStack? = nil)
        case rightMenu(RightMenuStack? = nil)

        static var `default`: Self {
            .deviceMainView
        }

        var isLeftMenu: Bool {
            if case .leftMenu = self {
                return true
            }
            return false
        }

        var isRightMenu: Bool {
            if case .rightMenu = self {
                return true
            }
            return false
        }
    }
Peter Suwara
  • 781
  • 10
  • 16
0

In Swift 5, you can use the Equatable protocol to compare the enums like below:

enum MyEnum: Equatable {
    case firstCase(String)
    case secondCase(Int)
    case thirdCase(Bool)
}

let enum1 = MyEnum.firstCase("hello")
let enum2 = MyEnum.firstCase("hello")
let enum3 = MyEnum.firstCase("world")


if enum1 == enum2 {
    print("enum1 and enum2 are equal")
} else {
    print("enum1 and enum2 are not equal")
}

if enum1 == enum3 {
    print("enum1 and enum3 are equal")
} else {
    print("enum1 and enum3 are not equal")
}

Here is the output:

enum1 and enum2 are equal
enum1 and enum3 are not equal
nitin.agam
  • 1,949
  • 1
  • 17
  • 24
-1

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")
}
Rachit
  • 814
  • 9
  • 19
  • Perfect place for a switch with two arguments. See above how this only takes one line of code per case. And your code fails for two numbers that are not equal. – gnasher729 Jul 19 '15 at 06:24