Parameterized Tests

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

Writing Smarter, Drier Tests

One of the guiding principles in software development is “Don’t Repeat Yourself” (DRY). This applies to test code just as much as it does to application code. A common anti-pattern in testing is writing multiple, nearly identical tests that check the same piece of logic with slightly different inputs. This leads to a bloated, hard-to-maintain test suite.

The Problem of Boilerplate

Imagine you have a function that validates email addresses. To test it thoroughly, you need to check multiple cases: a valid email, an email without an "@" symbol, an email without a domain, an email with invalid characters, and so on. Without parameterized tests, you would end up with a lot of repetitive code :  

let validator = EmailValidator()

@Test func testValidEmail() {
  #expect(validator.isValid("test@example.com") == true)
}

@Test func testMissingAtSymbol() {
  #expect(validator.isValid("testexample.com") == false)
}

@Test func testMissingDomain() {
  #expect(validator.isValid("test@") == false)
}

//... and so on for many more cases...

Introducing Parameterized Tests

Parameterized tests allow you to write a single test function and run it multiple times with different arguments. You provide the arguments directly in the @Test attribute using the arguments: parameter.  

@Test(
  "Test email validation with various valid inputs",
  arguments: [
    "test@example.com",
    "user.name+tag@domain.co.uk",
    "firstname.lastname@sub.domain.org"
  ]
)
func testValidEmails(email: String) {
  let validator = EmailValidator()
  #expect(validator.isValid(email) == true)
}

Debugging and Results

A key advantage of this feature is how Xcode presents the results. The Test Navigator doesn’t show a single test result; it treats each argument’s run as a distinct sub-test. If one of the arguments causes a failure, it will be clearly marked with a red ‘x’, pinpointing exactly which input is problematic. This makes debugging far more efficient than iterating through a loop inside a single test, where a failure on one iteration might not be clearly reported. You can even re-run the test for a single, specific failing argument directly from the Test Navigator.  

Advanced Argument Techniques

Swift Testing’s parameterization capabilities go beyond a single list of arguments.

@Test(
  "Test email validation with inputs and expected outcomes",
  arguments: zip(
    ["test@example.com", "invalid-email", "test@", ""],
    [true, false, false, false]
  )
)
func testEmailValidation(input: String, expectedResult: Bool) {
  let validator = EmailValidator()
  #expect(validator.isValid(input) == expectedResult)
}
@Test(
  arguments: , ["a", "b"]
)
func testCombinations(number: Int, letter: String) {
  // This will run 6 times:
  // (1, "a"), (1, "b")
  // (2, "a"), (2, "b")
  // (3, "a"), (3, "b")
}
See forum comments
Download course materials from Github
Previous: Advanced Test Customization with Traits Next: Conclusion