Skip to content

Swift Testing fundamentals and parameterized tests

Swift Testing centers each test on behavior rather than a test class lifecycle. @Test declares a test, #expect records an expectation, and #require stops the current test when later checks need a valid value.

Parameterize a rule

import Testing
@testable import FieldNotesDomain
@Suite("Tags")
struct TagTests {
@Test(
"normalizes valid tags",
arguments: [
(" Swift ", "swift"),
("iOS", "ios"),
("Field Notes", "field notes")
]
)
func normalizes(input: String, expected: String) throws {
let tag = try Tag(input)
#expect(tag.value == expected)
}
}

Use #require when later assertions need a non-optional value, such as the first matching note from a search result. The test syntax should follow the production contract instead of distorting it.

Keep cases focused

Arguments are useful when one behavior repeats across inputs. Separate valid normalization, length boundaries, and invalid input so a failure names the broken rule. Tags can group slow, database, or network tests; traits can control conditions and execution behavior.

Test asynchronous events

Async tests can await use cases directly. Confirmations are useful for callback-style events when the expected count matters. Avoid sleeping or broad timeouts as synchronization. Inject the dependency that determines completion and await its observable result.

Swift Testing and XCTest can coexist in one project. Choose by capability, not by a forced all-at-once migration.

Validation boundary

The sample uses Apple Swift Testing syntax but was not compiled in this content batch. The existing Field Notes companion package remains the executable testing checkpoint.

Series navigation

References