How to test equality of Swift enums with associated values
Solution 1:
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:
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:
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:
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:
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