Enumerations with associated values are my favorite feature of Swift. But how can we write unit tests against them? “Make them Equatable and use XCTAssertEqual” is common advice.
I’m here to argue otherwise. In fact, let’s use this as a jumping-off point to discuss Swift Equatables in unit tests.
When faced with the need to write unit tests, most Swift developers I’ve seen have an immediate response: make the type Equatable. This knee-jerk response creates a series of cascading problems:
XCTAssertEqualstatements right away, without first unit testing their Equatable implementations.
One of my colleagues has this Twitter bio: “Writing Equatable implementations at American Express”.
What’s wrong with putting Equatable implementations in production code? Nothing, as long as we need them for production code. Will the type be used in a sequence or collection? Then consider making it Equatable, depending on the algorithms you want to have available.
But not all types need proper value semantics. Most of the time, I’m just sending a bag of data from one point to another. Arguing “it should be Equatable, because it should have value semantics, because it’s a value object” strikes me as a violation of YAGNI: “You Aren’t Gonna Need It.” It’s coding for a future possibility which may never come. It’s waste.
Having written their Equatables, people have a tendency to use them right away. I want to say, “Whoa, slow down there.”
First, that’s not Test Driven Development. In TDD, a test comes first. Unless you’re laying out views, resist the urge to start by writing production code.
Second, whether the Equatable is placed in production code or in test code, it’s code. So it needs unit tests. If you put it in your test target, good for you. But in that case, you’re writing a test helper. “Who watches the watchmen?” That is, who tests the tests? One way is to extract helpers from existing tests. Another is to write tests for the helper, before using it.
But we shouldn’t write test helper code, and tests that use those helpers, at the same time. That’s asking for trouble.
So what do we do instead? Let’s look specifically at enumerations with associated values.
I’ve shared a screencast of how to start JSON parsing in Objective-C. But a naive translation of that code into Swift is unsatisfying. If we send a response model containing a status code, then the response handler will need to examine this status first. It needs to call some logic to determine whether the response represents a success or a failure.
In Swift, a better way is to encode this into an enumeration. Something like this:
T is the type of the response model. This not only forces response handler to consider success and failure up front. It also simplifies the response model, essentially moving the status code into the enumeration.
Let’s first write a test for a successful response. If the JSON contains a code of 200, we want the
parse method of the System Under Test to return success.
Instead of an Equatable, we’re using a
switch statement. If it’s a success, this test does nothing. For any other value, we fail. This code is resilient, and won’t have to be changed even if we add new enumeration values.
Notice what the test does with the success value’s associated response model: nothing. This particular test doesn’t care. If we had written this using
XCTAssertEqual, then we’d have to create an “expected value” of our response model. Every time a change was made to the response model, we’d have to update all tests that used it in an
The problem with equality is that it’s so exact. Sometimes you want it, of course. But for many unit tests, it’s overkill. It can over-specify something that really isn’t relevant to the outcome of the test. This results in fragile tests.
Testing for equality also inhibits Test Driven Development of aggregate types. TDD is a combination of Test-First with Incremental Design. I want to grow the JSON parsing code iteratively, using subsets of JSON that affect different slices of the response. This will happen gradually, over many tests. I want to avoid big-bang comparisons.
Let’s see if I can boil all this down to “Jon’s Testing Rules of Thumb”. (I should add, “for now,” because this is a process of discovery.)
XCTAssertEqualassertions can result in fragile tests.
I wouldn’t want to see similar switch statements repeated across tests. Ultimately, there’s probably a test helper waiting to be extracted. But that’s a topic for another time…
Questions, comments, concerns? Please leave a comment below!TDD Sample App: The Complete Collection …So Far]
Jon is a coach and consultant on iOS Clean Code (Test Driven Development, unit testing, refactoring, design). He’s been practicing TDD since 2001. You can learn more about his background, or see what services he can bring to your organization.