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
- Previous: Part 71: Testing strategy, seams, and confidence
- Next: Part 73: XCTest, XCUITest, test plans, and framework coexistence
- Series index: Zero to iOS Hero
References
- Swift Testing documents tests, suites, traits, expectations, and parameterization.
- Testing Swift packages covers SwiftPM test targets and commands.