Suppose I have:
protocol MyError: Error, Equatable {
var errorDispalyTitle: String { get }
var errorDisplayMessage: String { get }
}
enum ContentState {
case .loading
case .error(any MyError)
case .contentLoaded
}
If i were to implement Equatable
in ContentState
so I can compare during unit tests I end up with a problem because I have to compare two any MyError
types which are boxed and might be of two different underlying types.
extension ContentState: Equatable {
static func == (lhs: ContentState, rhs: ContentState) -> Bool {
switch (lhs, rhs) {
case (.loading, .loading):
return true
case (.contentLoaded, .contentLoaded):
return true
case (.error(let lhsError), .error(let rhsError)):
// TODO: have to compare if both underlying types are match and then if they are equal in value
default:
return false
}
}
}
How do I do this?
I tried lifting the generic from the existential type there to the type (e.g. ContentState<Error: MyError>
) which lets it compile when implementing equatable as it knows how to infer the type, but the problem is for whichever class uses that enum it doesnt matter which type is receiving of it, only that is any type of it, and if I don’t implement the any
existential it starts requiring the generic to be propagated up the chain.
3
Answers
You can write a wrapper that wraps an
Equatable
, and wrap the LHS and RHS errors before comparing.Then in your switch you can do:
Note that if
MyError
inherits fromHashable
instead ofEquatable
, you can use the builtinAnyHashable
instead of writing your ownAnyEquatable
.As of Swift 5.7, Swift automatically “opens” an existential when you pass it as an argument of generic type. The implicit
self
argument can be opened (in fact Swift has always opened theself
argument), and Swift can open multiple arguments in a single invocation. So we can write anisEqual(to:)
function that compares anyEquatable
to any otherEquatable
like this:And then we can complete your
ContentState
conformance like this:Four unit testing, I would recommend extending your type with
Bool
‘s as much as possible:, as I assume you’re mostly interested in testing if some object properly updates its state.
This makes unit tests easier to write and read, and they better transmit the intent.
If you want to also explicitly check the error, you could make use of generics:
, and assert on that: