0

I'd like to assert whether a value is a specific enum case.
For example, if I have the following enum class, and a variable let value: MyEnum:

enum MyEnum {
  case firstCase(value: Int)
  case secondCase
}

I'd like to check whether value is an instance of firstCase.
In essence, I'd like to be able to write the following or something equivalent:

let value: MyEnum = .firstCase(value: 3)
XCTAssertEnumCase(value, .firstCase)

How can I achieve this? I'm looking for an already existing XCT function, or for instructions how to write XCTAssertEnumCase myself.

Dávid Pásztor
  • 51,403
  • 9
  • 85
  • 116
nhaarman
  • 98,571
  • 55
  • 246
  • 278
  • https://forums.swift.org/t/xctassert-for-enum-cases/33365 ? – Larme Jul 20 '21 at 08:36
  • 1
    @Larme asserting case equality doesn't necessarily mean asserting equality. You often want your `Equatable` conformance to test the equality of the associated value of the enum cases as well, however, at the same time, you might need tests that only test that you have the same case of the enum without checking the associated value. – Dávid Pásztor Jul 20 '21 at 08:39
  • Do I understand you correctly that you do not want to do XCTAssertEqual(value, .firstCase(value: 3)) but rather match only against .firstCase? – Joakim Danielson Jul 20 '21 at 08:47
  • @JoakimDanielson Yes that is correct. – nhaarman Jul 20 '21 at 08:48

1 Answers1

4

You can easily create a function that works for specific enums, however, creating a generic assert function that works for any enums will be quite hard to achieve, because there's no protocol/type constraint that could represent any enum. You can use RawRepresentable for enums with raw values, but that won't cover all enums, such as the one in your question.

This is the function for your specific enum.

func XCTAssertEnumCase(_ testValue: MyEnum, _ expectedValue: MyEnum) -> Bool {
    switch (testValue, expectedValue) {
    case (.firstCase, .firstCase):
        return true
    case (.secondCase, .secondCase):
        return true
    default:
        return false
    }
}

Alternatively, you can make your enum conform to Equatable in your test target (if it doesn't already conform in your actual production target) and only check case equality in your Equatable conformance, but then you won't be able to easily test "full equality" (including the associated values). Also, the solution will require you to manually implement Equatable conformance for all protocols that you are testing.

You cannot instantiate an enum case that has an associated value without actually supplying an associated value. So XCTAssertEnumCase(value, .firstCase) cannot be achieved.

You can do XCTAssertEnumCase(testValue, .firstCase(value: 3313) where you can pass in any Int to the associated value of firstCase and as long as testValue is also firstCase, the func will return true, regardless of the associated values.

Alternatively, you could create separate functions for asserting each case of your enum.

extension MyEnum {
    func assertFirstCase() -> Bool {
        switch self {
        case .firstCase:
            return true
        default:
            return false
        }
    }

    func assertSecondCase() -> Bool {
        switch self {
        case .secondCase:
            return true
        default:
            return false
        }
    }
}

And then use it like this:

let value: MyEnum = .firstCase(value: 3)
value.assertFirstCase() // returns true
value.assertSecondCase() // returns false
Dávid Pásztor
  • 51,403
  • 9
  • 85
  • 116
  • Perhaps I'm misunderstanding, but your implementation of `XCTAssertEnumCase` does not allow me to invoke it like this, right? `XCTAssertEnumCase(value, .firstCase)`. I'll get an error saying `Member 'firstCase' expects argument of type 'Int'`? I'm explicitly looking for a way without having to supply the arguments for the `expectedValue`. – nhaarman Jul 20 '21 at 08:51
  • @nhaarman there is no way to achieve that. You cannot instantiate an enum case that has an associated value without supplying an associated value. However, you can pass in any default value and the function will discard it during the case equality checking. I know this doesn't 100% fulfil your needs, but unfortunately what you are looking for is simply not possibly in Swift. – Dávid Pásztor Jul 20 '21 at 08:57
  • That's a shame. Especially since it is possible to write `if case .firstCase = value`, but no way to extract this to a generic function. – nhaarman Jul 20 '21 at 09:12