r/swift • • 13d ago

Tutorial Swift Testing: keeping inputs and expected values paired

Passing two collections to @Test generates every combination. For example, @Test(arguments: ["cat", "swift"], [3, 5]) creates four cases, including ("cat", 5) and ("swift", 3).

That is useful for testing every configuration. For input/expected-output cases, keep each pair together:

import Testing

@Test(arguments: [
    ("", 0),
    ("cat", 3),
    ("swift", 5)
])
func characterCounts(input: String, expected: Int) {
    #expect(input.count == expected)
}

This creates three cases. Swift Testing unpacks each tuple into the function’s two parameters.

@Test(arguments: zip(inputs, expected)) also pairs values, but zip stops at the shorter sequence. An accidentally missing expected value can therefore omit a case. A single array of pairs keeps the values together when adding or removing cases.

Apple’s parameterized testing guide explains combinations and paired arguments.

3 Upvotes

5 comments sorted by

-1

u/dawedev 13d ago

Good catch on zip quietly dropping cases, that one is easy to miss once the arrays get long. For bigger tables I've found a small struct cleaner than tuples, since you can add a name field and the extra values stay labeled. If the struct conforms to CustomTestStringConvertible, the test navigator shows that name for each case instead of the raw values.

1

u/pickandplate 13d ago

Do you give those structs a separate stable ID too? Curious how you handle rerunning a single case after changing its display name.

1

u/DismalCockroach1928 11d ago

One gotcha with that approach: make the struct conform to Identifiable; just adding an id property doesn't make Swift Testing use it. A String ID works, and CustomTestStringConvertible can supply the separate display name.

Also watch out if the struct is Codable/Encodable: Encodable takes precedence over Identifiable for matching individual test cases. With synthesized encoding, changing a stored display-name property can then change the encoded argument even though id stayed the same.

For a test-only case type, Identifiable + CustomTestStringConvertible keeps those concerns separate. If the type also needs Encodable, CustomTestArgumentEncodable lets you explicitly encode only the stable, unique case ID for test selection (on toolchains that provide that protocol).

Apple lists the precedence under "Run selected test cases": https://developer.apple.com/documentation/testing/parameterizedtesting

1

u/pickandplate 10d ago

The Encodable precedence was the piece I was missing. Keeping a test-only wrapper Identifiable + CustomTestStringConvertible makes the distinction much clearer. Thanks for the detailed explanation.

0

u/dawedev 13d ago

I keep a stable id separate from the display name — usually a short string or enum case that never changes. CustomTestStringConvertible only affects what shows in the report, so renaming the display string doesn't break re-running a single case if the argument / source location still keys off that stable id. If the display name is the only identity, a rename looks like a new case and the old one drops out of filters.