You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
A hands-on, comprehensive guide for migrating from XCTest to Swift Testing and mastering the new framework. This playbook integrates the latest patterns and best practices from WWDC 2024 and official Apple documentation to make your tests more powerful, expressive, and maintainable.
1. Migration & Tooling Baseline
Ensure your environment is set up for a smooth, gradual migration.
What
Why
Xcode 16 & Swift 6
Swift Testing is bundled with the latest toolchain. It leverages modern Swift features like macros, structured concurrency, and powerful type-system checks.
Keep XCTest Targets
Incremental Migration is Key. You can have XCTest and Swift Testing tests in the same target, allowing you to migrate file-by-file without breaking CI. Both frameworks can coexist.
Enable Parallel Execution
In your Test Plan, ensure "Use parallel execution" is enabled. Swift Testing runs tests in parallel by default, which dramatically speeds up test runs and helps surface hidden state dependencies that serial execution might miss.
Migration Action Items
Ensure all developer machines and CI runners are on macOS 15+ and Xcode 16+.
For projects supporting Linux/Windows, add the swift-testing SPM package to your Package.swift. It's bundled in Xcode and not needed for Apple platforms.
For existing test targets, you must explicitly enable the framework. In the target's Build Settings, find Enable Testing Frameworks and set its value to Yes. Without this, import Testing will fail.
In your primary test plan, confirm that “Use parallel execution” is enabled. This is the default and recommended setting.
2. Expressive Assertions: #expect & #require
Replace the entire XCTAssert family with two powerful, expressive macros. They accept regular Swift expressions, eliminating the need for dozens of specialized XCTAssert functions.
Macro
Use Case & Behavior
#expect(expression)
Soft Check. Use for most validations. If the expression is false, the issue is recorded, but the test function continues executing. This allows you to find multiple failures in a single run.
#require(expression)
Hard Check. Use for critical preconditions (e.g., unwrapping an optional). If the expression is false or throws, the test is immediately aborted. This prevents cascading failures from an invalid state.
Power Move: Visual Failure Diagnostics
Unlike XCTAssert, which often only reports that a comparison failed, #expect shows you the exact values that caused the failure, directly in the IDE and logs. This visual feedback is a massive productivity boost.
Code:
@Test("User count meets minimum requirement")func testUserCount(){letuserCount=5
// This check will fail
#expect(userCount >10)}
Failure Output in Xcode:
▽ Expected expression to be true
#expect(userCount > 10)
| | |
5 | 10
false
Power Move: Optional-Safe Unwrapping
#require is the new, safer replacement for XCTUnwrap. It not only checks for nil but also unwraps the value for subsequent use.
Before: The XCTest Way
// In an XCTestCase subclass...
func testFetchUser_XCTest()asyncthrows{letuser=tryXCTUnwrap(awaitfetchUser(id:"123"),"Fetching user should not return nil")XCTAssertEqual(user.id,"123")}
After: The Swift Testing Way
@Test("Fetching a valid user succeeds")func testFetchUser()asyncthrows{
// #require both checks for nil and unwraps `user` in one step.
// If fetchUser returns nil, the test stops here and fails.
letuser=try #require(awaitfetchUser(id:"123"))
// `user` is now a non-optional User, ready for further assertions.
#expect(user.id =="123")
#expect(user.age ==37)}
Common Assertion Conversions Quick-Reference
Use this table as a cheat sheet when migrating your XCTest assertions.
XCTest Assertion
Swift Testing Equivalent
Notes
XCTAssert(expr)
#expect(expr)
Direct replacement for a boolean expression.
XCTAssertEqual(a, b)
#expect(a == b)
Use the standard == operator.
XCTAssertNotEqual(a, b)
#expect(a != b)
Use the standard != operator.
XCTAssertNil(a)
#expect(a == nil)
Direct comparison to nil.
XCTAssertNotNil(a)
#expect(a != nil)
Direct comparison to nil.
XCTAssertTrue(a)
#expect(a)
No change needed if a is already a Bool.
XCTAssertFalse(a)
#expect(!a)
Use the ! operator to negate the expression.
XCTAssertGreaterThan(a, b)
#expect(a > b)
Use any standard comparison operator: >, <, >=, <=
try XCTUnwrap(a)
try #require(a)
The preferred, safer way to unwrap optionals.
XCTAssertThrowsError(expr)
#expect(throws: (any Error).self) { try expr }
The basic form for checking any error.
XCTAssertNoThrow(try expr)
#expect(throws: Never.self) { try expr }
The explicit way to assert that no error is thrown.
Action Items
Run grep -R "XCTAssert\|XCTUnwrap" . to find all legacy assertions.
Convert try XCTUnwrap() calls to try #require(). This is a direct and superior replacement.
Convert most XCTAssert...() calls to #expect(). Use #require() only for preconditions where continuing the test makes no sense.
Group related checks logically within a test. Since #expect continues on failure, you can naturally check multiple properties of an object in a single test.
3. Setup, Teardown, and State Lifecycle
Swift Testing replaces setUpWithError and tearDownWithError with a more natural, type-safe lifecycle using init() and deinit.
The Core Concept: A fresh, new instance of the test suite (struct or class) is created for each test function it contains. This is the cornerstone of test isolation, guaranteeing that state from one test cannot leak into another.
Method
Replaces...
Behavior
init()
setUpWithError()
The initializer for your suite. Put all setup code here. It can be async and throws.
deinit
tearDownWithError()
The deinitializer. Put cleanup code here. It runs automatically after each test. Note:deinit is only available on class or actor suite types, not structs. This is a common reason to choose a class for your suite.
Practical Example: Migrating a Database Test Suite
After: The Swift Testing Way (using class for deinit)
@SuitefinalclassDatabaseServiceTests{
// Using a class here to demonstrate `deinit` for cleanup.
letsut:DatabaseServicelettempDirectory:URLinit()throws{
// ARRANGE: Runs before EACH test in this suite.
self.tempDirectory =FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)tryFileManager.default.createDirectory(at: tempDirectory, withIntermediateDirectories:true)lettestDatabase=TestDatabase(storageURL: tempDirectory)self.sut =DatabaseService(database: testDatabase)}deinit{
// TEARDOWN: Runs after EACH test.
try?FileManager.default.removeItem(at: tempDirectory)}@Testfunc testSavingUser()throws{letuser=User(id:"user-1", name:"Alex")try sut.save(user)
#expect(try sut.loadUser(id:"user-1")!=nil)}}
Action Items
Convert test classes from XCTestCase to structs (preferred for automatic state isolation) or final classes.
Move setUpWithError logic into the suite's init().
Move tearDownWithError logic into the suite's deinit (and use a class or actor if needed).
Define the SUT and its dependencies as let properties, initialized in init().
4. Mastering Error Handling
Go beyond do/catch with a dedicated, expressive API for validating thrown errors.
Overload
Replaces...
Example & Use Case
#expect(throws: (any Error).self)
Basic XCTAssertThrowsError
Verifies that any error was thrown.
#expect(throws: BrewingError.self)
Typed XCTAssertThrowsError
Ensures an error of a specific type is thrown.
#expect(throws: BrewingError.outOfBeans)
Specific Error XCTAssertThrowsError
Validates a specific error value is thrown.
Inspecting the Thrown Error
do/catch with switch
Payload Introspection. The ultimate tool for errors with associated values. The macro returns the error, which you can then inspect. swift let thrownError = #expect(throws: BrewingError.self) { try brew(beans: 0) } guard case let .notEnoughBeans(needed) = thrownError else { Issue.record("Wrong error case"); return } #expect(needed > 0)
#expect(throws: Never.self)
XCTAssertNoThrow
Explicitly asserts that a function does not throw. Ideal for happy-path tests.
Run a single test function with multiple argument sets to maximize coverage with minimal code. This is superior to a for-in loop because each argument set runs as an independent test, can be run in parallel, and failures are reported individually.
Pattern
How to Use It & When
Single Collection
@Test(arguments: [0, 100, -40]) The simplest form. Pass a collection of inputs.
Zipped Collections
@Test(arguments: zip(inputs, expectedOutputs)) The most common and powerful pattern. Use zip to pair inputs and expected outputs, ensuring a one-to-one correspondence.
Multiple Collections
@Test(arguments: ["USD", "EUR"], [1, 10, 100]) ⚠️ Caution: Cartesian Product. This creates a test case for every possible combination of arguments. Use it deliberately when you need to test all combinations.
Example: Migrating Repetitive Tests to a Parameterized One
Dynamically control which tests run based on feature flags, environment, or known issues.
Trait
What It Does & How to Use It
.disabled("Reason")
Unconditionally skips a test. The test is not run, but it is still compiled. Always provide a descriptive reason for CI visibility (e.g., "Flaky on CI, see FB12345").
.enabled(if: condition)
Conditionally runs a test. The test only runs if the boolean condition is true. This is perfect for tests tied to feature flags or specific environments. swift @Test(.enabled(if: FeatureFlags.isNewAPIEnabled)) func testNewAPI() { /* ... */ }
@available(...)
OS Version-Specific Tests. Apply this attribute directly to the test function. It's better than a runtime #available check because it allows the test runner to know the test is skipped for platform reasons, which is cleaner in test reports.
7. Writing Assertions with Standard Swift
Swift Testing's philosophy is to use plain Swift expressions for assertions. For more complex checks like unordered collections or floating-point numbers, use the power of the Swift standard library.
Assertion Type
How to Write It
Comparing Collections (Unordered)
A simple == check on arrays fails if elements are the same but the order is different. To check for equality while ignoring order, convert both collections to a Set.
Brittle:#expect(tags == ["ios", "swift"]) // Fails if tags are ["swift", "ios"] Robust:#expect(Set(tags) == Set(["swift", "ios"])) // Passes
Floating-Point Accuracy
Floating-point math is imprecise. #expect(0.1 + 0.2 == 0.3) will fail. To ensure tests are robust, check that the absolute difference between the values is within an acceptable tolerance.
Use suites and tags to manage large and complex test bases.
Suites and Nested Suites
A @Suite groups related tests and can be nested for a clear hierarchy. Traits applied to a suite are inherited by all tests and nested suites within it.
Tags for Cross-Cutting Concerns
Tags associate tests with common characteristics (e.g., .network, .ui, .regression) regardless of their suite. This is invaluable for filtering.
// Apply to a test or suite
@Test("Username validation",.tags(.fast,.regression))func testUsername(){ /* ... */ }
// Run from CLI
// swift test --filter .fast
// swift test --skip .flaky
// swift test --filter .networking --filter .regression
// Filter in Xcode Test Plan
// Add "fast" to the "Include Tags" field or "flaky" to the "Exclude Tags" field.
Power Move: Xcode UI Integration for Tags
Xcode 16 deeply integrates with tags, turning them into a powerful organizational tool.
Grouping by Tag in Test Navigator: In the Test Navigator (Cmd-6), click the tag icon at the top. This switches the view from the file hierarchy to one where tests are grouped by their tags. It's a fantastic way to visualize and run all tests related to a specific feature.
Test Report Insights: After a test run, the Test Report can automatically find patterns. Go to the Insights tab to see messages like "All 7 tests with the 'networking' tag failed." This immediately points you to systemic issues, saving significant debugging time.
9. Concurrency and Asynchronous Testing
Async/Await and Confirmations
Async Tests: Simply mark your test function async and use await.
Confirmations: To test APIs with completion handlers or that fire multiple times (like delegates or notifications), use the confirmation global function. It wraps the entire asynchronous operation and implicitly waits.
@Test("Delegate is notified 3 times")func testDelegateNotifications()async{
// The test operation is wrapped in an `await confirmation` call.
// It will automatically wait or fail if the count isn't met in time.
awaitconfirmation("delegate.didUpdate was called", expectedCount:3){ confirm in
// `confirm` is a function passed to your closure. Call it when the event happens.
letdelegate=MockDelegate{confirm() // Call the confirmation
}letsut=SystemUnderTest(delegate: delegate)
// The action that triggers the events happens *inside* the closure.
sut.performActionThatNotifiesThreeTimes()}}
Advanced Asynchronous Patterns
Asserting an Event Never Happens
Use a confirmation with expectedCount: 0 to verify that a callback or delegate method is never called during an operation. If confirm() is called, the test will fail.
@Test("Logging out does not trigger a data sync")func testLogoutDoesNotSync()async{awaitconfirmation("data sync was triggered", expectedCount:0){ confirm inletmockSyncEngine=MockSyncEngine{
// If this is ever called, the test will automatically fail.
confirm()}letsut=AccountManager(syncEngine: mockSyncEngine)
sut.logout()}}
Bridging Legacy Completion Handlers
For older asynchronous code that uses completion handlers, use withCheckedThrowingContinuation to wrap it in a modern async/await call that Swift Testing can work with.
func legacyFetch(completion:@escaping(Result<Data,Error>)->Void){
// ... legacy async code ...
}@Testfunc testLegacyFetch()asyncthrows{letdata=tryawaitwithCheckedThrowingContinuation{ continuation inlegacyFetch{ result in
continuation.resume(with: result)}}
#expect(!data.isEmpty)}
Controlling Parallelism
.serialized: Apply this trait to a @Test or @Suite to force its contents to run serially (one at a time). Use this as a temporary measure for legacy tests that are not thread-safe or have hidden state dependencies. The goal should be to refactor them to run in parallel.
.timeLimit: A safety net to prevent hung tests from stalling CI. The more restrictive (shorter) duration wins when applied at both the suite and test level.
10. Advanced API Cookbook
Feature
What it Does & How to Use It
withKnownIssue
Marks a test as an Expected Failure. It's better than .disabled for known bugs. The test still runs but won't fail the suite. Crucially, if the underlying bug gets fixed and the test passes, withKnownIssue will fail, alerting you to remove it.
CustomTestStringConvertible
Provides custom, readable descriptions for your types in test failure logs. Conform your key models to this protocol to make debugging much easier.
.bug(id: "JIRA-123") Trait
Associates a test directly with a ticket in your issue tracker. This adds invaluable context to test reports in Xcode and Xcode Cloud.
Test.current
A static property (Test.current) that gives you runtime access to the current test's metadata, such as its name, tags, and source location. Useful for advanced custom logging.
Multiple #expect Calls
Unlike XCTest where a failure might stop a test, #expect allows execution to continue. You can place multiple #expect calls in a single test to validate different properties of an object, and all failures will be reported together. There is no need for a special grouping macro.
11. Common Pitfalls and How to Avoid Them
A checklist of common mistakes developers make when adopting Swift Testing.
Overusing #require()
The Pitfall: Using #require() for every check. This makes tests brittle and hides information. If the first #require() fails, the rest of the test is aborted, and you won't know if other things were also broken.
The Fix: Use #expect() for most checks. Only use #require() for essential setup conditions where the rest of the test would be nonsensical if they failed (e.g., a non-nil SUT, a valid URL).
Forgetting State is Isolated
The Pitfall: Assuming that a property modified in one test will retain its value for the next test in the same suite.
The Fix: Remember that a new instance of the suite is created for every test. This is a feature, not a bug! All shared setup must happen in init(). Do not rely on state carrying over between tests.
Accidentally Using a Cartesian Product
The Pitfall: Passing multiple collections to a parameterized test without zip, causing an exponential explosion of test cases (@Test(arguments: collectionA, collectionB)).
The Fix: Be deliberate. If you want one-to-one pairing, always use zip. Only use the multi-collection syntax when you explicitly want to test every possible combination.
Ignoring the .serialized Trait for Unsafe Tests
The Pitfall: Migrating old, stateful tests that are not thread-safe and seeing them fail randomly due to parallel execution.
The Fix: As a temporary measure, apply the .serialized trait to the suite containing these tests. This forces them to run one-at-a-time, restoring the old behavior. The long-term goal should be to refactor the tests to be parallel-safe and remove the trait.
12. Migrating from XCTest
Swift Testing and XCTest can coexist in the same target, enabling an incremental migration.
Key Differences at a Glance
Feature
XCTest
Swift Testing
Test Discovery
Method name must start with test...
@Test attribute on any function or method.
Suite Type
class MyTests: XCTestCase
struct MyTests (preferred), class, or actor.
Assertions
XCTAssert...() family of functions
#expect() and #require() macros with Swift expressions.
Error Unwrapping
try XCTUnwrap(...)
try #require(...)
Setup/Teardown
setUpWithError(), tearDownWithError()
init(), deinit (on classes/actors)
Asynchronous Wait
XCTestExpectation and wait(for:timeout:)
await confirmation(...) { ... } block-based API.
Parallelism
Opt-in, multi-process
Opt-out, in-process via Swift Concurrency.
What NOT to Migrate (Yet)
Continue using XCTest for the following, as they are not currently supported by Swift Testing:
UI Automation Tests (using XCUIApplication)
Performance Tests (using XCTMetric and measure { ... })
Tests written in Objective-C
Appendix: Evergreen Testing Principles (The F.I.R.S.T. Principles)
These foundational principles are framework-agnostic, and Swift Testing is designed to make adhering to them easier than ever.
Principle
Meaning
Swift Testing Application
Fast
Tests must execute in milliseconds.
Lean on default parallelism. Use .serialized sparingly.
Isolated
Tests must not depend on each other.
Swift Testing enforces this by creating a new suite instance for every test. Random execution order helps surface violations.
Repeatable
A test must produce the same result every time.
Control all inputs (dates, network responses) with mocks/stubs. Reset state in init/deinit.
Self-Validating
The test must automatically report pass or fail.
Use #expect and #require. Never rely on print() for validation.
Timely
Write tests alongside the production code.
Use parameterized tests (@Test(arguments:)) to easily cover edge cases as you write code.
With Swift Testing you leverage powerful and expressive capabilities of the Swift programming language to develop tests with more confidence and less code. The library integrates seamlessly with Swift Package Manager testing workflow, supports flexible test organization, customizable metadata, and scalable test execution.
Define test functions almost anywhere with a single attribute.
Group related tests into hierarchies using Swift’s type system.
Integrate seamlessly with Swift concurrency.
Parameterize test functions across wide ranges of inputs.
Enable tests dynamically depending on runtime conditions.
Parallelize tests in-process.
Categorize tests using tags.
Associate bugs directly with the tests that verify their fixes or reproduce their problems.
A complex package or project may contain hundreds or thousands of tests and suites. Some subset of those tests may share some common facet, such as being critical or flaky. The testing library includes a type of trait called tags that you can add to group and categorize tests.
Tags are different from test suites: test suites impose structure on test functions at the source level, while tags provide semantic information for a test that can be shared with any number of other tests across test suites, source files, and even test targets.
To add a tag to a test, use the tags(_:) trait. This trait takes a sequence of tags as its argument, and those tags are then applied to the corresponding test at runtime. If any tags are applied to a test suite, then all tests in that suite inherit those tags.
The testing library doesn’t assign any semantic meaning to any tags, nor does the presence or absence of tags affect how the testing library runs tests.
Tags themselves are instances of Tag and expressed as named constants declared as static members of Tag. To declare a named constant tag, use the Tag() macro:
extension Tag {
@Tag static var legallyRequired: Self
}
@Test("Vendor's license is valid", .tags(.legallyRequired))
func licenseValid() { ... }
If two tags with the same name ( legallyRequired in the above example) are declared in different files, modules, or other contexts, the testing library treats them as equivalent.
If it’s important for a tag to be distinguished from similar tags declared elsewhere in a package or project (or its dependencies), use reverse-DNS naming to create a unique Swift symbol name for your tag:
extension Tag {
enum com_example_foodtruck {}
}
extension Tag.com_example_foodtruck {
@Tag static var extraSpecial: Tag
}
@Test(
"Extra Special Sauce recipe is secret",
.tags(.com_example_foodtruck.extraSpecial)
)
func secretSauce() { ... }
Tags must always be declared as members of Tag in an extension to that type or in a type nested within Tag. Redeclaring a tag under a second name has no effect and the additional name will not be recognized by the testing library. The following example is unsupported:
extension Tag {
@Tag static var legallyRequired: Self // ✅ OK: Declaring a new tag.
static var requiredByLaw: Self { // ❌ ERROR: This tag name isn't
// recognized at runtime.
legallyRequired
}
}
If a tag is declared as a named constant outside of an extension to the Tag type (for example, at the root of a file or in another unrelated type declaration), it cannot be applied to test functions or test suites. The following declarations are unsupported:
@Tag let needsKetchup: Self // ❌ ERROR: Tags must be declared in an extension
// to Tag.
struct Food {
@Tag var needsMustard: Self // ❌ ERROR: Tags must be declared in an extension
// to Tag.
}
Provide context or background information about the code’s purpose
Explain how complex code implemented
Include details which may be helpful when diagnosing issues
Test code is no different and can benefit from explanatory code comments, but often test issues are shown in places where the source code of the test is unavailable such as in continuous integration (CI) interfaces or in log files.
Seeing comments related to tests in these contexts can help diagnose issues more quickly. Comments can be added to test declarations and the testing library will automatically capture and show them when issues are recorded.
To include a comment on a test or suite, write an ordinary Swift code comment immediately before its @Test or @Suite attribute:
// Assumes the standard lunch menu includes a taco
@Test func lunchMenu() {
let foodTruck = FoodTruck(
menu: .lunch,
ingredients: [.tortillas, .cheese]
)
#expect(foodTruck.menu.contains { $0 is Taco })
}
The comment, // Assumes the standard lunch menu includes a taco, is added to the test.
The following language comment styles are supported:
Test comments which are automatically added from source code comments preserve their original formatting, including any prefixes like // or /**. This is because the whitespace and formatting of comments can be meaningful in some circumstances or aid in understanding the comment — for example, when a comment includes an example code snippet or diagram.
When working with a large selection of test functions, it can be helpful to organize them into test suites.
A test function can be added to a test suite in one of two ways:
By placing it in a Swift type.
By placing it in a Swift type and annotating that type with the @Suite attribute.
The @Suite attribute isn’t required for the testing library to recognize that a type contains test functions, but adding it allows customization of a test suite’s appearance in the IDE and at the command line. If a trait such as tags(_:) or disabled(_:sourceLocation:) is applied to a test suite, it’s automatically inherited by the tests contained in the suite.
In addition to containing test functions and any other members that a Swift type might contain, test suite types can also contain additional test suites nested within them. To add a nested test suite type, simply declare an additional type within the scope of the outer test suite type.
By default, tests contained within a suite run in parallel with each other. For more information about test parallelization, see Running tests serially or in parallel.
If a type contains a test function declared as an instance method (that is, without either the static or class keyword), the testing library calls that test function at runtime by initializing an instance of the type, then calling the test function on that instance. If a test suite type contains multiple test functions declared as instance methods, each one is called on a distinct instance of the type. Therefore, the following test suite and test function:
If a type contains test functions declared as instance methods, it must be possible to initialize an instance of the type with a zero-argument initializer. The initializer may be any combination of:
implicit or explicit
synchronous or asynchronous
throwing or non-throwing
private, fileprivate, internal, package, or public
For example:
@Suite struct FoodTruckTests {
var batteryLevel = 100
@Test func foodTruckExists() { ... } // ✅ OK: The type has an implicit init().
}
@Suite struct CashRegisterTests {
private init(cashOnHand: Decimal = 0.0) async throws { ... }
@Test func calculateSalesTax() { ... } // ✅ OK: The type has a callable init().
}
struct MenuTests {
var foods: [Food]
var prices: [Food: Decimal]
@Test static func specialOfTheDay() { ... } // ✅ OK: The function is static.
@Test func orderAllFoods() { ... } // ❌ ERROR: The suite type requires init().
}
The compiler emits an error when presented with a test suite that doesn’t meet this requirement.
Although @available can be applied to a test function to limit its availability at runtime, a test suite type (and any types that contain it) must not be annotated with the @available attribute:
@Suite struct FoodTruckTests { ... } // ✅ OK: The type is always available.
@available(macOS 11.0, *) // ❌ ERROR: The suite type must always be available.
@Suite struct CashRegisterTests { ... }
@available(macOS 11.0, *) struct MenuItemTests { // ❌ ERROR: The suite type's
// containing type must always
// be available too.
@Suite struct BurgerTests { ... }
}
The compiler emits an error when presented with a test suite that doesn’t meet this requirement.
The testing library checks whether a test argument conforms to this protocol, or any of several other known protocols, when running selected test cases. When a test argument conforms to this protocol, that conformance takes highest priority, and the testing library will then call encodeTestArgument(to:) on the argument. A type that conforms to this protocol is not required to conform to either Encodable or Decodable.
To declare a test function, write a Swift function declaration that doesn’t take any arguments, then prefix its name with the @Test attribute:
@Test func foodTruckExists() {
// Test logic goes here.
}
This test function can be present at file scope or within a type. A type containing test functions is automatically a test suite and can be optionally annotated with the @Suite attribute. For more information about suites, see Organizing test functions with suite types.
Note that, while this function is a valid test function, it doesn’t actually perform any action or test any code. To check for expected values and outcomes in test functions, add expectations to the test function.
As with other Swift functions, test functions can be marked async and throws to annotate them as concurrent or throwing, respectively. If a test is only safe to run in the main actor’s execution context (that is, from the main thread of the process), it can be annotated @MainActor:
If a test function can only run on newer versions of an operating system or of the Swift language, use the @available attribute when declaring it. Use the message argument of the @available attribute to specify a message to log if a test is unable to run due to limited availability:
@available(macOS 11.0, *)
@available(swift, introduced: 8.0, message: "Requires Swift 8.0 features to run")
@Test func foodTruckExists() { ... }
The testing library supports two distinct ways to identify a bug:
A URL linking to more information about the bug; and
A unique identifier in the bug’s associated bug-tracking system.
A bug may have both an associated URL and an associated unique identifier. It must have at least one or the other in order for the testing library to be able to interpret it correctly.
To create an instance of Bug with a URL, use the bug(_:_:) trait. At compile time, the testing library will validate that the given string can be parsed as a URL according to RFC 3986.
To create an instance of Bug with a bug’s unique identifier, use the bug(_:id:_:) trait. The testing library does not require that a bug’s unique identifier match any particular format, but will interpret unique identifiers starting with "FB" as referring to bugs tracked with the Apple Feedback Assistant. For convenience, you can also directly pass an integer as a bug’s identifier using bug(_:id:_:).
Some tests may naturally run slowly: they may require significant system resources to complete, may rely on downloaded data from a server, or may otherwise be dependent on external factors.
If a test may hang indefinitely or may consume too many system resources to complete effectively, consider setting a time limit for it so that it’s marked as failing if it runs for an excessive amount of time. Use the timeLimit(_:) trait as an upper bound:
@Test(.timeLimit(.minutes(60))
func serve100CustomersInOneHour() async {
for _ in 0 ..< 100 {
let customer = await Customer.next()
await customer.order()
...
}
}
The testing library may adjust the specified time limit for performance reasons or to ensure tests have enough time to run. In particular, a granularity of (by default) one minute is applied to tests. The testing library can also be configured with a maximum time limit per test that overrides any applied time limit traits.
When a time limit is applied to a parameterized test function, it’s applied to each invocation separately so that if only some arguments cause failures, then successful arguments aren’t incorrectly marked as failing too.
Provide custom scope for tests by implementing the scopeProvider(for:testCase:) method, returning a type that conforms to this protocol. Create a custom scope to consolidate common set-up and tear-down logic for tests which have similar needs, which allows each test function to focus on the unique aspects of its test.
The testing library defines a number of traits that you can add to test suites. You can also define your own traits by creating types that conform to this protocol, or to the TestTrait protocol.
The testing library defines a number of traits that can be added to test functions and to test suites. Define your own traits by creating types that conform to TestTrait or SuiteTrait:
Some tests need to be run over many different inputs. For instance, a test might need to validate all cases of an enumeration. The testing library lets developers specify one or more collections to iterate over during testing, with the elements of those collections being forwarded to a test function. An invocation of a test function with a particular set of argument values is called a test case.
By default, the test cases of a test function run in parallel with each other. For more information about test parallelization, see Running tests serially or in parallel.
It is very common to want to run a test n times over an array containing the values that should be tested. Consider the following test function:
enum Food {
case burger, iceCream, burrito, noodleBowl, kebab
}
@Test("All foods available")
func foodsAvailable() async throws {
for food: Food in [.burger, .iceCream, .burrito, .noodleBowl, .kebab] {
let foodTruck = FoodTruck(selling: food)
#expect(await foodTruck.cook(food))
}
}
If this test function fails for one of the values in the array, it may be unclear which value failed. Instead, the test function can be parameterized over the various inputs:
When passing a collection to the @Test attribute for parameterization, the testing library passes each element in the collection, one at a time, to the test function as its first (and only) argument. Then, if the test fails for one or more inputs, the corresponding diagnostics can clearly indicate which inputs to examine.
It’s possible to test more than one collection. Consider the following test function:
@Test("Can make large orders", arguments: Food.allCases, 1 ... 100)
func makeLargeOrder(of food: Food, count: Int) async throws {
let foodTruck = FoodTruck(selling: food)
#expect(await foodTruck.cook(food, quantity: count))
}
Elements from the first collection are passed as the first argument to the test function, elements from the second collection are passed as the second argument, and so forth.
Assuming there are five cases in the Food enumeration, this test function will, when run, be invoked 500 times (5 x 100) with every possible combination of food and order size. These combinations are referred to as the collections’ Cartesian product.
To avoid the combinatoric semantics shown above, use zip():
@Test("Can make large orders", arguments: zip(Food.allCases, 1 ... 100))
func makeLargeOrder(of food: Food, count: Int) async throws {
let foodTruck = FoodTruck(selling: food)
#expect(await foodTruck.cook(food, quantity: count))
}
The zipped sequence will be “destructured” into two arguments automatically, then passed to the test function for evaluation.
This revised test function is invoked once for each tuple in the zipped sequence, for a total of five invocations instead of 500 invocations. In other words, this test function is passed the inputs (.burger, 1), (.iceCream, 2), …, (.kebab, 5) instead of (.burger, 1), (.burger, 2), (.burger, 3), …, (.kebab, 99), (.kebab, 100).
If a parameterized test meets certain requirements, the testing library allows people to run specific test cases it contains. This can be useful when a test has many cases but only some are failing since it enables re-running and debugging the failing cases in isolation.
To support running selected test cases, it must be possible to deterministically match the test case’s arguments. When someone attempts to run selected test cases of a parameterized test function, the testing library evaluates each argument of the tests’ cases for conformance to one of several known protocols, and if all arguments of a test case conform to one of those protocols, that test case can be run selectively. The following lists the known protocols, in precedence order (highest to lowest):
Pass built-in traits to test functions or suite types to comment, categorize, classify, and modify the runtime behavior of test suites and test functions. Implement the TestTrait, and SuiteTrait protocols to create your own types that customize the behavior of your tests.
Values whose types conform to this protocol use it to describe themselves when they are present as part of the output of a test. For example, this protocol affects the display of values that are passed as arguments to test functions or that are elements of an expectation failure.
By default, the testing library converts values to strings using String(describing:). The resulting string may be inappropriate for some types and their values. If the type of the value is made to conform to CustomTestStringConvertible, then the value of its testDescription property will be used instead.
For example, consider the following type:
enum Food: CaseIterable {
case paella, oden, ragu
}
If an array of cases from this enumeration is passed to a parameterized test function:
The testing library provides much of the same functionality of XCTest, but uses its own syntax to declare test functions and types. Here, you’ll learn how to convert XCTest-based content to use the testing library instead.
XCTest and the testing library are available from different modules. Instead of importing the XCTest module, import the Testing module:
// Before
import XCTest
// After
import Testing
A single source file can contain tests written with XCTest as well as other tests written with the testing library. Import both XCTest and Testing if a source file contains mixed test content.
XCTest groups related sets of test methods in test classes: classes that inherit from the XCTestCase class provided by the XCTest framework. The testing library doesn’t require that test functions be instance members of types. Instead, they can be free or global functions, or can be static or class members of a type.
If you want to group your test functions together, you can do so by placing them in a Swift type. The testing library refers to such a type as a suite. These types do not need to be classes, and they don’t inherit from XCTestCase.
To convert a subclass of XCTestCase to a suite, remove the XCTestCase conformance. It’s also generally recommended that a Swift structure or actor be used instead of a class because it allows the Swift compiler to better-enforce concurrency safety:
// Before
class FoodTruckTests: XCTestCase {
...
}
In XCTest, code can be scheduled to run before and after a test using the setUp() and tearDown() family of functions. When writing tests using the testing library, implement init() and/or deinit instead:
// Before
class FoodTruckTests: XCTestCase {
var batteryLevel: NSNumber!
override func setUp() async throws {
batteryLevel = 100
}
...
}
// After
struct FoodTruckTests {
var batteryLevel: NSNumber
init() async throws {
batteryLevel = 100
}
...
}
The use of async and throws is optional. If teardown is needed, declare your test suite as a class or as an actor rather than as a structure and implement deinit:
// Before
class FoodTruckTests: XCTestCase {
var batteryLevel: NSNumber!
override func setUp() async throws {
batteryLevel = 100
}
override func tearDown() {
batteryLevel = 0 // drain the battery
}
...
}
// After
final class FoodTruckTests {
var batteryLevel: NSNumber
init() async throws {
batteryLevel = 100
}
deinit {
batteryLevel = 0 // drain the battery
}
...
}
The testing library represents individual tests as functions, similar to how they are represented in XCTest. However, the syntax for declaring a test function is different. In XCTest, a test method must be a member of a test class and its name must start with test. The testing library doesn’t require a test function to have any particular name. Instead, it identifies a test function by the presence of the @Test attribute:
// Before
class FoodTruckTests: XCTestCase {
func testEngineWorks() { ... }
...
}
As with XCTest, the testing library allows test functions to be marked async, throws, or async- throws, and to be isolated to a global actor (for example, by using the @MainActor attribute.)
For more information about test functions and how to declare and customize them, see Defining test functions.
XCTest also has a function, XCTUnwrap(), that tests if an optional value is nil and throws an error if it is. When using the testing library, you can use require(_:_:sourceLocation:) with optional expressions to unwrap them:
// Before
func testEngineWorks() throws {
let engine = FoodTruck.shared.engine
let part = try XCTUnwrap(engine.parts.first)
...
}
// After
@Test func engineWorks() throws {
let engine = FoodTruck.shared.engine
let part = try #require(engine.parts.first)
...
}
XCTest has a function, XCTFail(), that causes a test to fail immediately and unconditionally. This function is useful when the syntax of the language prevents the use of an XCTAssert() function. To record an unconditional issue using the testing library, use the record(_:sourceLocation:) function:
// Before
func testEngineWorks() {
let engine = FoodTruck.shared.engine
guard case .electric = engine else {
XCTFail("Engine is not electric")
return
}
...
}
// After
@Test func engineWorks() {
let engine = FoodTruck.shared.engine
guard case .electric = engine else {
Issue.record("Engine is not electric")
return
}
...
}
The following table includes a list of the various XCTAssert() functions and their equivalents in the testing library:
XCTest
Swift Testing
XCTAssert(x), XCTAssertTrue(x)
#expect(x)
XCTAssertFalse(x)
#expect(!x)
XCTAssertNil(x)
#expect(x == nil)
XCTAssertNotNil(x)
#expect(x != nil)
XCTAssertEqual(x, y)
#expect(x == y)
XCTAssertNotEqual(x, y)
#expect(x != y)
XCTAssertIdentical(x, y)
#expect(x === y)
XCTAssertNotIdentical(x, y)
#expect(x !== y)
XCTAssertGreaterThan(x, y)
#expect(x > y)
XCTAssertGreaterThanOrEqual(x, y)
#expect(x >= y)
XCTAssertLessThanOrEqual(x, y)
#expect(x <= y)
XCTAssertLessThan(x, y)
#expect(x < y)
XCTAssertThrowsError(try f())
#expect(throws: (any Error).self) { try f() }
XCTAssertThrowsError(try f()) { error in … }
let error = #expect(throws: (any Error).self) { try f() }
An instance of an XCTestCase subclass can set its continueAfterFailure property to false to cause a test to stop running after a failure occurs. XCTest stops an affected test by throwing an Objective-C exception at the time the failure occurs.
The behavior of an exception thrown through a Swift stack frame is undefined. If an exception is thrown through an async Swift function, it typically causes the process to terminate abnormally, preventing other tests from running.
The testing library doesn’t use exceptions to stop test functions. Instead, use the require(_:_:sourceLocation:) macro, which throws a Swift error on failure:
When using either continueAfterFailure or require(_:_:sourceLocation:), other tests will continue to run after the failed test method or test function.
XCTest has a class, XCTestExpectation, that represents some asynchronous condition. You create an instance of this class (or a subclass like XCTKeyPathExpectation) using an initializer or a convenience method on XCTestCase. When the condition represented by an expectation occurs, the developer fulfills the expectation. Concurrently, the developer waits for the expectation to be fulfilled using an instance of XCTWaiter or using a convenience method on XCTestCase.
Wherever possible, prefer to use Swift concurrency to validate asynchronous conditions. For example, if it’s necessary to determine the result of an asynchronous Swift function, it can be awaited with await. For a function that takes a completion handler but which doesn’t use await, a Swift continuation can be used to convert the call into an async-compatible one.
Confirmations function similarly to the expectations API of XCTest, however, they don’t block or suspend the caller while waiting for a condition to be fulfilled. Instead, the requirement is expected to be confirmed (the equivalent of fulfilling an expectation) before confirmation() returns, and records an issue otherwise:
// Before
func testTruckEvents() async {
let soldFood = expectation(description: "…")
FoodTruck.shared.eventHandler = { event in
if case .soldFood = event {
soldFood.fulfill()
}
}
await Customer().buy(.soup)
await fulfillment(of: [soldFood])
...
}
// After
@Test func truckEvents() async {
await confirmation("…") { soldFood in
FoodTruck.shared.eventHandler = { event in
if case .soldFood = event {
soldFood()
}
}
await Customer().buy(.soup)
}
...
}
By default, XCTestExpectation expects to be fulfilled exactly once, and will record an issue in the current test if it is not fulfilled or if it is fulfilled more than once. Confirmation behaves the same way and expects to be confirmed exactly once by default. You can configure the number of times an expectation should be fulfilled by setting its expectedFulfillmentCount property, and you can pass a value for the expectedCount argument of confirmation(_:expectedCount:isolation:sourceLocation:_:) for the same purpose.
XCTestExpectation has a property, assertForOverFulfill, which when set to false allows an expectation to be fulfilled more times than expected without causing a test failure. When using a confirmation, you can pass a range to confirmation(_:expectedCount:isolation:sourceLocation:_:) as its expected count to indicate that it must be confirmed at least some number of times:
// Before
func testRegularCustomerOrders() async {
let soldFood = expectation(description: "…")
soldFood.expectedFulfillmentCount = 10
soldFood.assertForOverFulfill = false
FoodTruck.shared.eventHandler = { event in
if case .soldFood = event {
soldFood.fulfill()
}
}
for customer in regularCustomers() {
await customer.buy(customer.regularOrder)
}
await fulfillment(of: [soldFood])
...
}
// After
@Test func regularCustomerOrders() async {
await confirmation(
"…",
expectedCount: 10...
) { soldFood in
FoodTruck.shared.eventHandler = { event in
if case .soldFood = event {
soldFood()
}
}
for customer in regularCustomers() {
await customer.buy(customer.regularOrder)
}
}
...
}
Any range expression with a lower bound (that is, whose type conforms to both RangeExpression<Int> and Sequence<Int>) can be used with confirmation(_:expectedCount:isolation:sourceLocation:_:). You must specify a lower bound for the number of confirmations because, without one, the testing library cannot tell if an issue should be recorded when there have been zero confirmations.
When using XCTest, the XCTSkip error type can be thrown to bypass the remainder of a test function. As well, the XCTSkipIf() and XCTSkipUnless() functions can be used to conditionalize the same action. The testing library allows developers to skip a test function or an entire test suite before it starts running using the ConditionTrait trait type. Annotate a test suite or test function with an instance of this trait type to control whether it runs:
// Before
class FoodTruckTests: XCTestCase {
func testArepasAreTasty() throws {
try XCTSkipIf(CashRegister.isEmpty)
try XCTSkipUnless(FoodTruck.sells(.arepas))
...
}
...
}
A test may have a known issue that sometimes or always prevents it from passing. When written using XCTest, such tests can call XCTExpectFailure(_:options:failingBlock:) to tell XCTest and its infrastructure that the issue shouldn’t cause the test to fail. The testing library has an equivalent function with synchronous and asynchronous variants:
This function can be used to annotate a section of a test as having a known issue:
// Before
func testGrillWorks() async {
XCTExpectFailure("Grill is out of fuel") {
try FoodTruck.shared.grill.start()
}
...
}
// After
@Test func grillWorks() async {
withKnownIssue("Grill is out of fuel") {
try FoodTruck.shared.grill.start()
}
...
}
If a test may fail intermittently, the call to XCTExpectFailure(_:options:failingBlock:) can be marked non-strict. When using the testing library, specify that the known issue is intermittent instead:
// Before
func testGrillWorks() async {
XCTExpectFailure(
"Grill may need fuel",
options: .nonStrict()
) {
try FoodTruck.shared.grill.start()
}
...
}
// After
@Test func grillWorks() async {
withKnownIssue(
"Grill may need fuel",
isIntermittent: true
) {
try FoodTruck.shared.grill.start()
}
...
}
Additional options can be specified when calling XCTExpectFailure():
isEnabled can be set to false to skip known-issue matching (for instance, if a particular issue only occurs under certain conditions)
issueMatcher can be set to a closure to allow marking only certain issues as known and to allow other issues to be recorded as test failures
The testing library includes overloads of withKnownIssue() that take additional arguments with similar behavior:
By default, the testing library runs all tests in a suite in parallel. The default behavior of XCTest is to run each test in a suite sequentially. If your tests use shared state such as global variables, you may see unexpected behavior including unreliable test outcomes when you run tests in parallel.
Annotate your test suite with serialized to run tests within that suite serially:
The testing library defines a number of traits that you can add to test functions. You can also define your own traits by creating types that conform to this protocol, or to the SuiteTrait protocol.
When you add this trait to a parameterized test function, that test runs its cases serially instead of in parallel. This trait has no effect when you apply it to a non-parameterized test function.
When you add this trait to a test suite, that suite runs its contained test functions (including their cases, when parameterized) and sub-suites serially instead of in parallel. If the sub-suites have children, they also run serially.
This trait does not affect the execution of a test relative to its peers or to unrelated tests. This trait has no effect if you disable test parallelization globally (for example, by passing --no-parallel to the swift test command.)
By default, tests run in parallel with respect to each other. Parallelization is accomplished by the testing library using task groups, and tests generally all run in the same process. The number of tests that run concurrently is controlled by the Swift runtime.
Parallelization can be disabled on a per-function or per-suite basis using the serialized trait:
@Test(.serialized, arguments: Food.allCases) func prepare(food: Food) {
// This function will be invoked serially, once per food, because it has the
// .serialized trait.
}
@Suite(.serialized) struct FoodTruckTests {
@Test(arguments: Condiment.allCases) func refill(condiment: Condiment) {
// This function will be invoked serially, once per condiment, because the
// containing suite has the .serialized trait.
}
@Test func startEngine() async throws {
// This function will not run while refill(condiment:) is running. One test
// must end before the other will start.
}
}
When added to a parameterized test function, this trait causes that test to run its cases serially instead of in parallel. When applied to a non-parameterized test function, this trait has no effect. When applied to a test suite, this trait causes that suite to run its contained test functions and sub-suites serially instead of in parallel.
This trait is recursively applied: if it is applied to a suite, any parameterized tests or test suites contained in that suite are also serialized (as are any tests contained in those suites, and so on.)
This trait doesn’t affect the execution of a test relative to its peers or to unrelated tests. This trait has no effect if test parallelization is globally disabled (by, for example, passing --no-parallel to the swift test command.)
Often, a test is only applicable in specific circumstances. For instance, you might want to write a test that only runs on devices with particular hardware capabilities, or performs locale-dependent operations. The testing library allows you to add traits to your tests that cause runners to automatically skip them if conditions like these are not met.
If it’s currently winter, then presumably ice cream won’t be available for sale and this test will fail. It therefore makes sense to only enable it if it’s currently summer. You can conditionally enable a test with enabled(if:_:sourceLocation:):
It’s also possible to conditionally disable a test and to combine multiple conditions:
@Test(
"Ice cream is cold",
.enabled(if: Season.current == .summer),
.disabled("We ran out of sprinkles")
)
func isCold() async throws { ... }
If a test is disabled because of a problem for which there is a corresponding bug report, you can use one of these functions to show the relationship between the test and the bug report:
For example, the following test cannot run due to bug number "12345":
@Test(
"Ice cream is cold",
.enabled(if: Season.current == .summer),
.disabled("We ran out of sprinkles"),
.bug(id: "12345")
)
func isCold() async throws { ... }
If a test has multiple conditions applied to it, they must all pass for it to run. Otherwise, the test notes the first condition to fail as the reason the test is skipped.
Use a Confirmation to confirm the occurrence of an asynchronous event that you can’t check directly using an expectation. For more information, see Testing asynchronous code.
To validate that your code produces an expected value, use expect(_:_:sourceLocation:). This macro captures the expression you pass, and provides detailed information when the code doesn’t satisfy the expectation.
@Test func returningCustomerRemembersUsualOrder() throws {
let customer = try #require(Customer(id: 123))
// The test runner doesn't reach this line if the customer is nil.
#expect(customer.usualOrder.countOfItems == 2)
}
Tests allow developers to prove that the code they write is working as expected. If code isn’t working correctly, bug trackers are often used to track the work necessary to fix the underlying problem. It’s often useful to associate specific bugs with tests that reproduce them or verify they are fixed.
A bug’s URL is passed as a string and must be parseable according to RFC 3986. A bug’s unique identifier can be passed as an integer or as a string. For more information on the formats recognized by the testing library, see Interpreting bug identifiers.
A bug’s unique identifier or URL may be insufficient to uniquely and clearly identify a bug associated with a test. Bug trackers universally provide a “title” field for bugs that is not visible to the testing library. To add a bug’s title to a test, include it after the bug’s unique identifier or URL:
@Test(
"Food truck has napkins",
.bug(id: "12345", "Forgot to buy more napkins")
)
func hasNapkins() async {
...
}
Use this type to provide context or background information about a test’s purpose, explain how a complex test operates, or include details which may be helpful when diagnosing issues recorded by a test.
To add a comment to a test or suite, add a code comment before its @Test or @Suite attribute. See Adding comments to tests for more details.
Associate a time limit with tests by using timeLimit(_:).
If a test has more than one time limit associated with it, the value of this property is the shortest one. If a test has no time limits associated with it, the value of this property is nil.
Use this tag with members of the Tag type declared in an extension to mark them as usable with tests. For more information on declaring tags, see Adding tags to tests.
The value of this property is equal to the name of the symbol to which the Test attribute is applied (that is, the name of the type or function.) To get the customized display name specified as part of the Test attribute, use the displayName property.
If the value is true, then the testing library applies this trait recursively to child test suites and test functions. Otherwise, it only applies the trait to the test suite to which you added the trait.
By default, traits are not recursively applied to children.
The name of the source file is derived from this instance’s fileID property. It consists of the substring of the file ID after the last forward-slash character ( "/".) For example, if the value of this instance’s fileID property is "FoodTruck/WheelTests.swift", the file name is "WheelTests.swift".
The structure of file IDs is described in the documentation for #fileID in the Swift standard library.
Use this type to specify a test timeout with TimeLimitTrait. TimeLimitTrait uses this type instead of Swift’s built-in Duration type because the testing library doesn’t support high-precision, arbitrarily short durations for test timeouts. The smallest unit of time you can specify in a Duration is minutes.
Instances of Test attached to types rather than functions are test suites. They do not contain any test logic of their own, but they may have traits added to them that also apply to their subtests.
A test suite can be declared using the Suite(_:_:) macro.
The name of the module is derived from this instance’s fileID property. It consists of the substring of the file ID up to the first forward-slash character ( "/".) For example, if the value of this instance’s fileID property is "FoodTruck/WheelTests.swift", the module name is "FoodTruck".
The structure of file IDs is described in the documentation for the #fileID macro in the Swift standard library.
The testing library integrates with Swift concurrency, meaning that in many situations you can test asynchronous code using standard Swift features. Mark your test function as async and, in the function body, await any asynchronous interactions:
Call confirmation(_:expectedCount:isolation:sourceLocation:_:) in your asynchronous test function to create a Confirmation for the expected event. In the trailing closure parameter, call the code under test. Swift Testing passes a Confirmation as the parameter to the closure, which you call as a function in the event handler for the code under test when the event you’re testing for occurs:
@Test("OrderCalculator successfully calculates subtotal for no pizzas")
func subtotalForNoPizzas() async {
let calculator = OrderCalculator()
await confirmation() { confirmation in
calculator.successHandler = { _ in confirmation() }
_ = await calculator.subtotal(for: PizzaToppings(bases: []))
}
}
If you expect the event to happen more than once, set the expectedCount parameter to the number of expected occurrences. The test passes if the number of occurrences during the test matches the expected count, and fails otherwise.
@Test("Customers bought sandwiches")
func boughtSandwiches() async {
await confirmation(expectedCount: 0 ..< 1000) { boughtSandwich in
var foodTruck = FoodTruck()
foodTruck.orderHandler = { order in
if order.contains(.sandwich) {
boughtSandwich()
}
}
await FoodTruck.operate()
}
}
In this example, there may be zero customers or up to (but not including) 1,000 customers who order sandwiches. Any range expression which includes an explicit lower bound can be used:
Range Expression
Usage
1...
If an event must occur at least once
5...
If an event must occur at least five times
1 ... 5
If an event must occur at least once, but not more than five times
0 ..< 100
If an event may or may not occur, but must not occur more than 99 times
This preserves the list of the tags exactly as they were originally specified, in their original order, including duplicate entries. To access the complete, unique set of tags applied to a Test, see tags.
If the current task is running a test, or is a subtask of another task that is running a test, the value of this property describes the test’s currently-running case. If no test is currently running, the value of this property is nil.
If the current task is detached from a task that started running a test, or if the current thread was created without using Swift concurrency (e.g. by using Thread.detachNewThread(_:) or DispatchQueue.async(execute:)), the value of this property may be nil.
When you add this trait to a parameterized test function, that test runs its cases serially instead of in parallel. This trait has no effect when you apply it to a non-parameterized test function.
When you add this trait to a test suite, that suite runs its contained test functions (including their cases, when parameterized) and sub-suites serially instead of in parallel. If the sub-suites have children, they also run serially.
This trait does not affect the execution of a test relative to its peers or to unrelated tests. This trait has no effect if you disable test parallelization globally (for example, by passing --no-parallel to the swift test command.)
The default type is Never, which can’t be instantiated. The scopeProvider(for:testCase:)-cjmg method for any trait with Never as its test scope provider type must return nil, meaning that the trait doesn’t provide a custom scope for tests it’s applied to.
The default type is Never, which can’t be instantiated. The scopeProvider(for:testCase:)-cjmg method for any trait with Never as its test scope provider type must return nil, meaning that the trait doesn’t provide a custom scope for tests it’s applied to.
Test timeouts do not support high-precision, arbitrarily short durations due to variability in testing environments. You express the duration in minutes, with a minimum duration of one minute.
When you associate this trait with a test, that test must complete within a time limit of, at most, timeLimit. If the test runs longer, the testing library records a Issue.Kind.timeLimitExceeded(timeLimitComponents:) issue, which it treats as a test failure.
The testing library can use a shorter time limit than that specified by timeLimit if you configure it to enforce a maximum per-test limit. When you configure a maximum per-test limit, the time limit of the test this trait is applied to is the shorter of timeLimit and the maximum per-test limit. For information on configuring maximum per-test limits, consult the documentation for the tool you use to run your tests.
If a test is parameterized, this time limit is applied to each of its test cases individually. If a test has more than one time limit associated with it, the testing library uses the shortest time limit.
If the value is true, then the testing library applies this trait recursively to child test suites and test functions. Otherwise, it only applies the trait to the test suite to which you added the trait.
By default, traits are not recursively applied to children.
The testing library calls this method after it discovers all tests and their traits, and before it begins to run any tests. Use this method to prepare necessary internal state, or to determine whether the test should run.
The default implementation of this method does nothing.
The testing library calls this method after it discovers all tests and their traits, and before it begins to run any tests. Use this method to prepare necessary internal state, or to determine whether the test should run.
The default implementation of this method does nothing.
A value conforming to TestScopeProvider which you use to provide custom scoping for test or testCase. Returns nil if the trait doesn’t provide any custom scope for the test or test case.
If this trait’s type conforms to TestScoping, the default value returned by this method depends on the values of test and testCase:
If test represents a suite, this trait must conform to SuiteTrait. If the value of this suite trait’s isRecursive property is true, then this method returns nil, and the suite trait provides its custom scope once for each test function the test suite contains. If the value of isRecursive is false, this method returns self, and the suite trait provides its custom scope once for the entire test suite.
If test represents a test function, this trait also conforms to TestTrait. If testCase is nil, this method returns nil; otherwise, it returns self. This means that by default, a trait which is applied to or inherited by a test function provides its custom scope once for each of that function’s cases.
A trait may override this method to further customize the default behaviors above. For example, if a trait needs to provide custom test scope both once per-suite and once per-test function in that suite, it implements the method to return a non- nil scope provider under those conditions.
A trait may also implement this method and return nil if it determines that it does not need to provide a custom scope for a particular test at runtime, even if the test has the trait applied. This can improve performance and make diagnostics clearer by avoiding an unnecessary call to provideScope(for:testCase:performing:).
If this trait’s type does not conform to TestScoping and its associated TestScopeProvider type is the default Never, then this method returns nil by default. This means that instances of this trait don’t provide a custom scope for tests to which they’re applied.
Implement this method to conform to the Hashable protocol. The components used for hashing must be the same as the components compared in your type’s == operator implementation. Call hasher.combine(_:) with each of these components.
This function is the only requirement of the Comparable protocol. The remainder of the relational operator functions are implemented by the standard library for any type that conforms to Comparable.
By default, tests run in parallel with respect to each other. Parallelization is accomplished by the testing library using task groups, and tests generally all run in the same process. The number of tests that run concurrently is controlled by the Swift runtime.
Parallelization can be disabled on a per-function or per-suite basis using the serialized trait:
@Test(.serialized, arguments: Food.allCases) func prepare(food: Food) {
// This function will be invoked serially, once per food, because it has the
// .serialized trait.
}
@Suite(.serialized) struct FoodTruckTests {
@Test(arguments: Condiment.allCases) func refill(condiment: Condiment) {
// This function will be invoked serially, once per condiment, because the
// containing suite has the .serialized trait.
}
@Test func startEngine() async throws {
// This function will not run while refill(condiment:) is running. One test
// must end before the other will start.
}
}
When added to a parameterized test function, this trait causes that test to run its cases serially instead of in parallel. When applied to a non-parameterized test function, this trait has no effect. When applied to a test suite, this trait causes that suite to run its contained test functions and sub-suites serially instead of in parallel.
This trait is recursively applied: if it is applied to a suite, any parameterized tests or test suites contained in that suite are also serialized (as are any tests contained in those suites, and so on.)
This trait doesn’t affect the execution of a test relative to its peers or to unrelated tests. This trait has no effect if test parallelization is globally disabled (by, for example, passing --no-parallel to the swift test command.)
The customized display name of this test. If the value of this argument is nil, the display name of the test is derived from the associated function’s name.
traits
Zero or more traits to apply to this test.
zippedCollections
Two zipped collections of values to pass to testFunction.
Because all errors thrown by body are caught as known issues, this function is not throwing. If only some errors or issues are known to occur while others should continue to cause test failures, use withKnownIssue(_:isIntermittent:sourceLocation:_:when:matching:) instead.
An optional comment to apply to any issues generated by this function.
expectedCount
The number of times the expected event should occur when body is invoked. The default value of this argument is 1, indicating that the event should occur exactly once. Pass 0 if the event should never occur when body is invoked.
sourceLocation
The source location to which any recorded issues should be attributed.
Use confirmations to check that an event occurs while a test is running in complex scenarios where #expect() and #require() are insufficient. For example, a confirmation may be useful when an expected event occurs:
In a context that cannot be awaited by the calling function such as an event handler or delegate callback;
More than once, or never; or
As a callback that is invoked as part of a larger operation.
To use a confirmation, pass a closure containing the work to be performed. The testing library will then pass an instance of Confirmation to the closure. Every time the event in question occurs, the closure should call the confirmation:
let n = 10
await confirmation("Baked buns", expectedCount: n) { bunBaked in
foodTruck.eventHandler = { event in
if event == .baked(.cinnamonBun) {
bunBaked()
}
}
await foodTruck.bake(.cinnamonBun, count: n)
}
When the closure returns, the testing library checks if the confirmation’s preconditions have been met, and records an issue if they have not.
A closure that contains the trait’s custom condition logic. If this closure returns false, the trait allows the test to run. Otherwise, the testing library skips the test.
A closure that contains the trait’s custom condition logic. If this closure returns false, the trait allows the test to run. Otherwise, the testing library skips the test.
A closure that contains the trait’s custom condition logic. If this closure returns true, the trait allows the test to run. Otherwise, the testing library skips the test.
The customized display name of this test. If the value of this argument is nil, the display name of the test is derived from the associated function’s name.
traits
Zero or more traits to apply to this test.
collection
A collection of values to pass to the associated test function.
If condition evaluates to false, an Issue is recorded for the test that is running in the current task and an instance of ExpectationFailedError is thrown.
A closure that contains the trait’s custom condition logic. If this closure returns true, the trait allows the test to run. Otherwise, the testing library skips the test.
Whether or not the known issue occurs intermittently. If this argument is true and the known issue does not occur, no secondary issue is recorded.
sourceLocation
The source location to which any recorded issues should be attributed.
body
The function to invoke.
precondition
A function that determines if issues are known to occur during the execution of body. If this function returns true, encountered issues that are matched by issueMatcher are considered to be known issues; if this function returns false, issueMatcher is not called and they are treated as unknown.
issueMatcher
A function to invoke when an issue occurs that is used to determine if the issue is known to occur. By default, all issues match.
Use this function when a test is known to raise one or more issues that should not cause the test to fail, or if a precondition affects whether issues are known to occur. For example:
It is not necessary to specify both precondition and issueMatcher if only one is relevant. If all errors and issues should be considered known issues, use withKnownIssue(_:isIntermittent:sourceLocation:_:) instead.
The customized display name of this test. If the value of this argument is nil, the display name of the test is derived from the associated function’s name.
traits
Zero or more traits to apply to this test.
collection1
A collection of values to pass to testFunction.
collection2
A second collection of values to pass to testFunction.
The customized display name of this test. If the value of this argument is nil, the display name of the test is derived from the associated function’s name.
With Swift Testing you leverage powerful and expressive capabilities of the Swift programming language to develop tests with more confidence and less code. The library integrates seamlessly with Swift Package Manager testing workflow, supports flexible test organization, customizable metadata, and scalable test execution.
Define test functions almost anywhere with a single attribute.
Group related tests into hierarchies using Swift’s type system.
Integrate seamlessly with Swift concurrency.
Parameterize test functions across wide ranges of inputs.
Enable tests dynamically depending on runtime conditions.
Parallelize tests in-process.
Categorize tests using tags.
Associate bugs directly with the tests that verify their fixes or reproduce their problems.
A complex package or project may contain hundreds or thousands of tests and suites. Some subset of those tests may share some common facet, such as being critical or flaky. The testing library includes a type of trait called tags that you can add to group and categorize tests.
Tags are different from test suites: test suites impose structure on test functions at the source level, while tags provide semantic information for a test that can be shared with any number of other tests across test suites, source files, and even test targets.
To add a tag to a test, use the tags(_:) trait. This trait takes a sequence of tags as its argument, and those tags are then applied to the corresponding test at runtime. If any tags are applied to a test suite, then all tests in that suite inherit those tags.
The testing library doesn’t assign any semantic meaning to any tags, nor does the presence or absence of tags affect how the testing library runs tests.
Tags themselves are instances of Tag and expressed as named constants declared as static members of Tag. To declare a named constant tag, use the Tag() macro:
extension Tag {
@Tag static var legallyRequired: Self
}
@Test("Vendor's license is valid", .tags(.legallyRequired))
func licenseValid() { ... }
If two tags with the same name ( legallyRequired in the above example) are declared in different files, modules, or other contexts, the testing library treats them as equivalent.
If it’s important for a tag to be distinguished from similar tags declared elsewhere in a package or project (or its dependencies), use reverse-DNS naming to create a unique Swift symbol name for your tag:
extension Tag {
enum com_example_foodtruck {}
}
extension Tag.com_example_foodtruck {
@Tag static var extraSpecial: Tag
}
@Test(
"Extra Special Sauce recipe is secret",
.tags(.com_example_foodtruck.extraSpecial)
)
func secretSauce() { ... }
Tags must always be declared as members of Tag in an extension to that type or in a type nested within Tag. Redeclaring a tag under a second name has no effect and the additional name will not be recognized by the testing library. The following example is unsupported:
extension Tag {
@Tag static var legallyRequired: Self // ✅ OK: Declaring a new tag.
static var requiredByLaw: Self { // ❌ ERROR: This tag name isn't
// recognized at runtime.
legallyRequired
}
}
If a tag is declared as a named constant outside of an extension to the Tag type (for example, at the root of a file or in another unrelated type declaration), it cannot be applied to test functions or test suites. The following declarations are unsupported:
@Tag let needsKetchup: Self // ❌ ERROR: Tags must be declared in an extension
// to Tag.
struct Food {
@Tag var needsMustard: Self // ❌ ERROR: Tags must be declared in an extension
// to Tag.
}
Provide context or background information about the code’s purpose
Explain how complex code implemented
Include details which may be helpful when diagnosing issues
Test code is no different and can benefit from explanatory code comments, but often test issues are shown in places where the source code of the test is unavailable such as in continuous integration (CI) interfaces or in log files.
Seeing comments related to tests in these contexts can help diagnose issues more quickly. Comments can be added to test declarations and the testing library will automatically capture and show them when issues are recorded.
To include a comment on a test or suite, write an ordinary Swift code comment immediately before its @Test or @Suite attribute:
// Assumes the standard lunch menu includes a taco
@Test func lunchMenu() {
let foodTruck = FoodTruck(
menu: .lunch,
ingredients: [.tortillas, .cheese]
)
#expect(foodTruck.menu.contains { $0 is Taco })
}
The comment, // Assumes the standard lunch menu includes a taco, is added to the test.
The following language comment styles are supported:
Test comments which are automatically added from source code comments preserve their original formatting, including any prefixes like // or /**. This is because the whitespace and formatting of comments can be meaningful in some circumstances or aid in understanding the comment — for example, when a comment includes an example code snippet or diagram.
When working with a large selection of test functions, it can be helpful to organize them into test suites.
A test function can be added to a test suite in one of two ways:
By placing it in a Swift type.
By placing it in a Swift type and annotating that type with the @Suite attribute.
The @Suite attribute isn’t required for the testing library to recognize that a type contains test functions, but adding it allows customization of a test suite’s appearance in the IDE and at the command line. If a trait such as tags(_:) or disabled(_:sourceLocation:) is applied to a test suite, it’s automatically inherited by the tests contained in the suite.
In addition to containing test functions and any other members that a Swift type might contain, test suite types can also contain additional test suites nested within them. To add a nested test suite type, simply declare an additional type within the scope of the outer test suite type.
By default, tests contained within a suite run in parallel with each other. For more information about test parallelization, see Running tests serially or in parallel.
If a type contains a test function declared as an instance method (that is, without either the static or class keyword), the testing library calls that test function at runtime by initializing an instance of the type, then calling the test function on that instance. If a test suite type contains multiple test functions declared as instance methods, each one is called on a distinct instance of the type. Therefore, the following test suite and test function:
If a type contains test functions declared as instance methods, it must be possible to initialize an instance of the type with a zero-argument initializer. The initializer may be any combination of:
implicit or explicit
synchronous or asynchronous
throwing or non-throwing
private, fileprivate, internal, package, or public
For example:
@Suite struct FoodTruckTests {
var batteryLevel = 100
@Test func foodTruckExists() { ... } // ✅ OK: The type has an implicit init().
}
@Suite struct CashRegisterTests {
private init(cashOnHand: Decimal = 0.0) async throws { ... }
@Test func calculateSalesTax() { ... } // ✅ OK: The type has a callable init().
}
struct MenuTests {
var foods: [Food]
var prices: [Food: Decimal]
@Test static func specialOfTheDay() { ... } // ✅ OK: The function is static.
@Test func orderAllFoods() { ... } // ❌ ERROR: The suite type requires init().
}
The compiler emits an error when presented with a test suite that doesn’t meet this requirement.
Although @available can be applied to a test function to limit its availability at runtime, a test suite type (and any types that contain it) must not be annotated with the @available attribute:
@Suite struct FoodTruckTests { ... } // ✅ OK: The type is always available.
@available(macOS 11.0, *) // ❌ ERROR: The suite type must always be available.
@Suite struct CashRegisterTests { ... }
@available(macOS 11.0, *) struct MenuItemTests { // ❌ ERROR: The suite type's
// containing type must always
// be available too.
@Suite struct BurgerTests { ... }
}
The compiler emits an error when presented with a test suite that doesn’t meet this requirement.
The testing library checks whether a test argument conforms to this protocol, or any of several other known protocols, when running selected test cases. When a test argument conforms to this protocol, that conformance takes highest priority, and the testing library will then call encodeTestArgument(to:) on the argument. A type that conforms to this protocol is not required to conform to either Encodable or Decodable.
To declare a test function, write a Swift function declaration that doesn’t take any arguments, then prefix its name with the @Test attribute:
@Test func foodTruckExists() {
// Test logic goes here.
}
This test function can be present at file scope or within a type. A type containing test functions is automatically a test suite and can be optionally annotated with the @Suite attribute. For more information about suites, see Organizing test functions with suite types.
Note that, while this function is a valid test function, it doesn’t actually perform any action or test any code. To check for expected values and outcomes in test functions, add expectations to the test function.
As with other Swift functions, test functions can be marked async and throws to annotate them as concurrent or throwing, respectively. If a test is only safe to run in the main actor’s execution context (that is, from the main thread of the process), it can be annotated @MainActor:
If a test function can only run on newer versions of an operating system or of the Swift language, use the @available attribute when declaring it. Use the message argument of the @available attribute to specify a message to log if a test is unable to run due to limited availability:
@available(macOS 11.0, *)
@available(swift, introduced: 8.0, message: "Requires Swift 8.0 features to run")
@Test func foodTruckExists() { ... }
The testing library supports two distinct ways to identify a bug:
A URL linking to more information about the bug; and
A unique identifier in the bug’s associated bug-tracking system.
A bug may have both an associated URL and an associated unique identifier. It must have at least one or the other in order for the testing library to be able to interpret it correctly.
To create an instance of Bug with a URL, use the bug(_:_:) trait. At compile time, the testing library will validate that the given string can be parsed as a URL according to RFC 3986.
To create an instance of Bug with a bug’s unique identifier, use the bug(_:id:_:) trait. The testing library does not require that a bug’s unique identifier match any particular format, but will interpret unique identifiers starting with "FB" as referring to bugs tracked with the Apple Feedback Assistant. For convenience, you can also directly pass an integer as a bug’s identifier using bug(_:id:_:).
Some tests may naturally run slowly: they may require significant system resources to complete, may rely on downloaded data from a server, or may otherwise be dependent on external factors.
If a test may hang indefinitely or may consume too many system resources to complete effectively, consider setting a time limit for it so that it’s marked as failing if it runs for an excessive amount of time. Use the timeLimit(_:) trait as an upper bound:
@Test(.timeLimit(.minutes(60))
func serve100CustomersInOneHour() async {
for _ in 0 ..< 100 {
let customer = await Customer.next()
await customer.order()
...
}
}
The testing library may adjust the specified time limit for performance reasons or to ensure tests have enough time to run. In particular, a granularity of (by default) one minute is applied to tests. The testing library can also be configured with a maximum time limit per test that overrides any applied time limit traits.
When a time limit is applied to a parameterized test function, it’s applied to each invocation separately so that if only some arguments cause failures, then successful arguments aren’t incorrectly marked as failing too.
Provide custom scope for tests by implementing the scopeProvider(for:testCase:) method, returning a type that conforms to this protocol. Create a custom scope to consolidate common set-up and tear-down logic for tests which have similar needs, which allows each test function to focus on the unique aspects of its test.
The testing library defines a number of traits that you can add to test suites. You can also define your own traits by creating types that conform to this protocol, or to the TestTrait protocol.
The testing library defines a number of traits that can be added to test functions and to test suites. Define your own traits by creating types that conform to TestTrait or SuiteTrait:
Some tests need to be run over many different inputs. For instance, a test might need to validate all cases of an enumeration. The testing library lets developers specify one or more collections to iterate over during testing, with the elements of those collections being forwarded to a test function. An invocation of a test function with a particular set of argument values is called a test case.
By default, the test cases of a test function run in parallel with each other. For more information about test parallelization, see Running tests serially or in parallel.
It is very common to want to run a test n times over an array containing the values that should be tested. Consider the following test function:
enum Food {
case burger, iceCream, burrito, noodleBowl, kebab
}
@Test("All foods available")
func foodsAvailable() async throws {
for food: Food in [.burger, .iceCream, .burrito, .noodleBowl, .kebab] {
let foodTruck = FoodTruck(selling: food)
#expect(await foodTruck.cook(food))
}
}
If this test function fails for one of the values in the array, it may be unclear which value failed. Instead, the test function can be parameterized over the various inputs:
When passing a collection to the @Test attribute for parameterization, the testing library passes each element in the collection, one at a time, to the test function as its first (and only) argument. Then, if the test fails for one or more inputs, the corresponding diagnostics can clearly indicate which inputs to examine.
It’s possible to test more than one collection. Consider the following test function:
@Test("Can make large orders", arguments: Food.allCases, 1 ... 100)
func makeLargeOrder(of food: Food, count: Int) async throws {
let foodTruck = FoodTruck(selling: food)
#expect(await foodTruck.cook(food, quantity: count))
}
Elements from the first collection are passed as the first argument to the test function, elements from the second collection are passed as the second argument, and so forth.
Assuming there are five cases in the Food enumeration, this test function will, when run, be invoked 500 times (5 x 100) with every possible combination of food and order size. These combinations are referred to as the collections’ Cartesian product.
To avoid the combinatoric semantics shown above, use zip():
@Test("Can make large orders", arguments: zip(Food.allCases, 1 ... 100))
func makeLargeOrder(of food: Food, count: Int) async throws {
let foodTruck = FoodTruck(selling: food)
#expect(await foodTruck.cook(food, quantity: count))
}
The zipped sequence will be “destructured” into two arguments automatically, then passed to the test function for evaluation.
This revised test function is invoked once for each tuple in the zipped sequence, for a total of five invocations instead of 500 invocations. In other words, this test function is passed the inputs (.burger, 1), (.iceCream, 2), …, (.kebab, 5) instead of (.burger, 1), (.burger, 2), (.burger, 3), …, (.kebab, 99), (.kebab, 100).
If a parameterized test meets certain requirements, the testing library allows people to run specific test cases it contains. This can be useful when a test has many cases but only some are failing since it enables re-running and debugging the failing cases in isolation.
To support running selected test cases, it must be possible to deterministically match the test case’s arguments. When someone attempts to run selected test cases of a parameterized test function, the testing library evaluates each argument of the tests’ cases for conformance to one of several known protocols, and if all arguments of a test case conform to one of those protocols, that test case can be run selectively. The following lists the known protocols, in precedence order (highest to lowest):
Pass built-in traits to test functions or suite types to comment, categorize, classify, and modify the runtime behavior of test suites and test functions. Implement the TestTrait, and SuiteTrait protocols to create your own types that customize the behavior of your tests.
Values whose types conform to this protocol use it to describe themselves when they are present as part of the output of a test. For example, this protocol affects the display of values that are passed as arguments to test functions or that are elements of an expectation failure.
By default, the testing library converts values to strings using String(describing:). The resulting string may be inappropriate for some types and their values. If the type of the value is made to conform to CustomTestStringConvertible, then the value of its testDescription property will be used instead.
For example, consider the following type:
enum Food: CaseIterable {
case paella, oden, ragu
}
If an array of cases from this enumeration is passed to a parameterized test function:
The testing library provides much of the same functionality of XCTest, but uses its own syntax to declare test functions and types. Here, you’ll learn how to convert XCTest-based content to use the testing library instead.
XCTest and the testing library are available from different modules. Instead of importing the XCTest module, import the Testing module:
// Before
import XCTest
// After
import Testing
A single source file can contain tests written with XCTest as well as other tests written with the testing library. Import both XCTest and Testing if a source file contains mixed test content.
XCTest groups related sets of test methods in test classes: classes that inherit from the XCTestCase class provided by the XCTest framework. The testing library doesn’t require that test functions be instance members of types. Instead, they can be free or global functions, or can be static or class members of a type.
If you want to group your test functions together, you can do so by placing them in a Swift type. The testing library refers to such a type as a suite. These types do not need to be classes, and they don’t inherit from XCTestCase.
To convert a subclass of XCTestCase to a suite, remove the XCTestCase conformance. It’s also generally recommended that a Swift structure or actor be used instead of a class because it allows the Swift compiler to better-enforce concurrency safety:
// Before
class FoodTruckTests: XCTestCase {
...
}
In XCTest, code can be scheduled to run before and after a test using the setUp() and tearDown() family of functions. When writing tests using the testing library, implement init() and/or deinit instead:
// Before
class FoodTruckTests: XCTestCase {
var batteryLevel: NSNumber!
override func setUp() async throws {
batteryLevel = 100
}
...
}
// After
struct FoodTruckTests {
var batteryLevel: NSNumber
init() async throws {
batteryLevel = 100
}
...
}
The use of async and throws is optional. If teardown is needed, declare your test suite as a class or as an actor rather than as a structure and implement deinit:
// Before
class FoodTruckTests: XCTestCase {
var batteryLevel: NSNumber!
override func setUp() async throws {
batteryLevel = 100
}
override func tearDown() {
batteryLevel = 0 // drain the battery
}
...
}
// After
final class FoodTruckTests {
var batteryLevel: NSNumber
init() async throws {
batteryLevel = 100
}
deinit {
batteryLevel = 0 // drain the battery
}
...
}
The testing library represents individual tests as functions, similar to how they are represented in XCTest. However, the syntax for declaring a test function is different. In XCTest, a test method must be a member of a test class and its name must start with test. The testing library doesn’t require a test function to have any particular name. Instead, it identifies a test function by the presence of the @Test attribute:
// Before
class FoodTruckTests: XCTestCase {
func testEngineWorks() { ... }
...
}
As with XCTest, the testing library allows test functions to be marked async, throws, or async- throws, and to be isolated to a global actor (for example, by using the @MainActor attribute.)
For more information about test functions and how to declare and customize them, see Defining test functions.
XCTest also has a function, XCTUnwrap(), that tests if an optional value is nil and throws an error if it is. When using the testing library, you can use require(_:_:sourceLocation:) with optional expressions to unwrap them:
// Before
func testEngineWorks() throws {
let engine = FoodTruck.shared.engine
let part = try XCTUnwrap(engine.parts.first)
...
}
// After
@Test func engineWorks() throws {
let engine = FoodTruck.shared.engine
let part = try #require(engine.parts.first)
...
}
XCTest has a function, XCTFail(), that causes a test to fail immediately and unconditionally. This function is useful when the syntax of the language prevents the use of an XCTAssert() function. To record an unconditional issue using the testing library, use the record(_:sourceLocation:) function:
// Before
func testEngineWorks() {
let engine = FoodTruck.shared.engine
guard case .electric = engine else {
XCTFail("Engine is not electric")
return
}
...
}
// After
@Test func engineWorks() {
let engine = FoodTruck.shared.engine
guard case .electric = engine else {
Issue.record("Engine is not electric")
return
}
...
}
The following table includes a list of the various XCTAssert() functions and their equivalents in the testing library:
XCTest
Swift Testing
XCTAssert(x), XCTAssertTrue(x)
#expect(x)
XCTAssertFalse(x)
#expect(!x)
XCTAssertNil(x)
#expect(x == nil)
XCTAssertNotNil(x)
#expect(x != nil)
XCTAssertEqual(x, y)
#expect(x == y)
XCTAssertNotEqual(x, y)
#expect(x != y)
XCTAssertIdentical(x, y)
#expect(x === y)
XCTAssertNotIdentical(x, y)
#expect(x !== y)
XCTAssertGreaterThan(x, y)
#expect(x > y)
XCTAssertGreaterThanOrEqual(x, y)
#expect(x >= y)
XCTAssertLessThanOrEqual(x, y)
#expect(x <= y)
XCTAssertLessThan(x, y)
#expect(x < y)
XCTAssertThrowsError(try f())
#expect(throws: (any Error).self) { try f() }
XCTAssertThrowsError(try f()) { error in … }
let error = #expect(throws: (any Error).self) { try f() }
An instance of an XCTestCase subclass can set its continueAfterFailure property to false to cause a test to stop running after a failure occurs. XCTest stops an affected test by throwing an Objective-C exception at the time the failure occurs.
The behavior of an exception thrown through a Swift stack frame is undefined. If an exception is thrown through an async Swift function, it typically causes the process to terminate abnormally, preventing other tests from running.
The testing library doesn’t use exceptions to stop test functions. Instead, use the require(_:_:sourceLocation:) macro, which throws a Swift error on failure:
When using either continueAfterFailure or require(_:_:sourceLocation:), other tests will continue to run after the failed test method or test function.
XCTest has a class, XCTestExpectation, that represents some asynchronous condition. You create an instance of this class (or a subclass like XCTKeyPathExpectation) using an initializer or a convenience method on XCTestCase. When the condition represented by an expectation occurs, the developer fulfills the expectation. Concurrently, the developer waits for the expectation to be fulfilled using an instance of XCTWaiter or using a convenience method on XCTestCase.
Wherever possible, prefer to use Swift concurrency to validate asynchronous conditions. For example, if it’s necessary to determine the result of an asynchronous Swift function, it can be awaited with await. For a function that takes a completion handler but which doesn’t use await, a Swift continuation can be used to convert the call into an async-compatible one.
Confirmations function similarly to the expectations API of XCTest, however, they don’t block or suspend the caller while waiting for a condition to be fulfilled. Instead, the requirement is expected to be confirmed (the equivalent of fulfilling an expectation) before confirmation() returns, and records an issue otherwise:
// Before
func testTruckEvents() async {
let soldFood = expectation(description: "…")
FoodTruck.shared.eventHandler = { event in
if case .soldFood = event {
soldFood.fulfill()
}
}
await Customer().buy(.soup)
await fulfillment(of: [soldFood])
...
}
// After
@Test func truckEvents() async {
await confirmation("…") { soldFood in
FoodTruck.shared.eventHandler = { event in
if case .soldFood = event {
soldFood()
}
}
await Customer().buy(.soup)
}
...
}
By default, XCTestExpectation expects to be fulfilled exactly once, and will record an issue in the current test if it is not fulfilled or if it is fulfilled more than once. Confirmation behaves the same way and expects to be confirmed exactly once by default. You can configure the number of times an expectation should be fulfilled by setting its expectedFulfillmentCount property, and you can pass a value for the expectedCount argument of confirmation(_:expectedCount:isolation:sourceLocation:_:) for the same purpose.
XCTestExpectation has a property, assertForOverFulfill, which when set to false allows an expectation to be fulfilled more times than expected without causing a test failure. When using a confirmation, you can pass a range to confirmation(_:expectedCount:isolation:sourceLocation:_:) as its expected count to indicate that it must be confirmed at least some number of times:
// Before
func testRegularCustomerOrders() async {
let soldFood = expectation(description: "…")
soldFood.expectedFulfillmentCount = 10
soldFood.assertForOverFulfill = false
FoodTruck.shared.eventHandler = { event in
if case .soldFood = event {
soldFood.fulfill()
}
}
for customer in regularCustomers() {
await customer.buy(customer.regularOrder)
}
await fulfillment(of: [soldFood])
...
}
// After
@Test func regularCustomerOrders() async {
await confirmation(
"…",
expectedCount: 10...
) { soldFood in
FoodTruck.shared.eventHandler = { event in
if case .soldFood = event {
soldFood()
}
}
for customer in regularCustomers() {
await customer.buy(customer.regularOrder)
}
}
...
}
Any range expression with a lower bound (that is, whose type conforms to both RangeExpression<Int> and Sequence<Int>) can be used with confirmation(_:expectedCount:isolation:sourceLocation:_:). You must specify a lower bound for the number of confirmations because, without one, the testing library cannot tell if an issue should be recorded when there have been zero confirmations.
When using XCTest, the XCTSkip error type can be thrown to bypass the remainder of a test function. As well, the XCTSkipIf() and XCTSkipUnless() functions can be used to conditionalize the same action. The testing library allows developers to skip a test function or an entire test suite before it starts running using the ConditionTrait trait type. Annotate a test suite or test function with an instance of this trait type to control whether it runs:
// Before
class FoodTruckTests: XCTestCase {
func testArepasAreTasty() throws {
try XCTSkipIf(CashRegister.isEmpty)
try XCTSkipUnless(FoodTruck.sells(.arepas))
...
}
...
}
A test may have a known issue that sometimes or always prevents it from passing. When written using XCTest, such tests can call XCTExpectFailure(_:options:failingBlock:) to tell XCTest and its infrastructure that the issue shouldn’t cause the test to fail. The testing library has an equivalent function with synchronous and asynchronous variants:
This function can be used to annotate a section of a test as having a known issue:
// Before
func testGrillWorks() async {
XCTExpectFailure("Grill is out of fuel") {
try FoodTruck.shared.grill.start()
}
...
}
// After
@Test func grillWorks() async {
withKnownIssue("Grill is out of fuel") {
try FoodTruck.shared.grill.start()
}
...
}
If a test may fail intermittently, the call to XCTExpectFailure(_:options:failingBlock:) can be marked non-strict. When using the testing library, specify that the known issue is intermittent instead:
// Before
func testGrillWorks() async {
XCTExpectFailure(
"Grill may need fuel",
options: .nonStrict()
) {
try FoodTruck.shared.grill.start()
}
...
}
// After
@Test func grillWorks() async {
withKnownIssue(
"Grill may need fuel",
isIntermittent: true
) {
try FoodTruck.shared.grill.start()
}
...
}
Additional options can be specified when calling XCTExpectFailure():
isEnabled can be set to false to skip known-issue matching (for instance, if a particular issue only occurs under certain conditions)
issueMatcher can be set to a closure to allow marking only certain issues as known and to allow other issues to be recorded as test failures
The testing library includes overloads of withKnownIssue() that take additional arguments with similar behavior:
By default, the testing library runs all tests in a suite in parallel. The default behavior of XCTest is to run each test in a suite sequentially. If your tests use shared state such as global variables, you may see unexpected behavior including unreliable test outcomes when you run tests in parallel.
Annotate your test suite with serialized to run tests within that suite serially:
The testing library defines a number of traits that you can add to test functions. You can also define your own traits by creating types that conform to this protocol, or to the SuiteTrait protocol.
When you add this trait to a parameterized test function, that test runs its cases serially instead of in parallel. This trait has no effect when you apply it to a non-parameterized test function.
When you add this trait to a test suite, that suite runs its contained test functions (including their cases, when parameterized) and sub-suites serially instead of in parallel. If the sub-suites have children, they also run serially.
This trait does not affect the execution of a test relative to its peers or to unrelated tests. This trait has no effect if you disable test parallelization globally (for example, by passing --no-parallel to the swift test command.)
By default, tests run in parallel with respect to each other. Parallelization is accomplished by the testing library using task groups, and tests generally all run in the same process. The number of tests that run concurrently is controlled by the Swift runtime.
Parallelization can be disabled on a per-function or per-suite basis using the serialized trait:
@Test(.serialized, arguments: Food.allCases) func prepare(food: Food) {
// This function will be invoked serially, once per food, because it has the
// .serialized trait.
}
@Suite(.serialized) struct FoodTruckTests {
@Test(arguments: Condiment.allCases) func refill(condiment: Condiment) {
// This function will be invoked serially, once per condiment, because the
// containing suite has the .serialized trait.
}
@Test func startEngine() async throws {
// This function will not run while refill(condiment:) is running. One test
// must end before the other will start.
}
}
When added to a parameterized test function, this trait causes that test to run its cases serially instead of in parallel. When applied to a non-parameterized test function, this trait has no effect. When applied to a test suite, this trait causes that suite to run its contained test functions and sub-suites serially instead of in parallel.
This trait is recursively applied: if it is applied to a suite, any parameterized tests or test suites contained in that suite are also serialized (as are any tests contained in those suites, and so on.)
This trait doesn’t affect the execution of a test relative to its peers or to unrelated tests. This trait has no effect if test parallelization is globally disabled (by, for example, passing --no-parallel to the swift test command.)
Often, a test is only applicable in specific circumstances. For instance, you might want to write a test that only runs on devices with particular hardware capabilities, or performs locale-dependent operations. The testing library allows you to add traits to your tests that cause runners to automatically skip them if conditions like these are not met.
If it’s currently winter, then presumably ice cream won’t be available for sale and this test will fail. It therefore makes sense to only enable it if it’s currently summer. You can conditionally enable a test with enabled(if:_:sourceLocation:):
It’s also possible to conditionally disable a test and to combine multiple conditions:
@Test(
"Ice cream is cold",
.enabled(if: Season.current == .summer),
.disabled("We ran out of sprinkles")
)
func isCold() async throws { ... }
If a test is disabled because of a problem for which there is a corresponding bug report, you can use one of these functions to show the relationship between the test and the bug report:
For example, the following test cannot run due to bug number "12345":
@Test(
"Ice cream is cold",
.enabled(if: Season.current == .summer),
.disabled("We ran out of sprinkles"),
.bug(id: "12345")
)
func isCold() async throws { ... }
If a test has multiple conditions applied to it, they must all pass for it to run. Otherwise, the test notes the first condition to fail as the reason the test is skipped.
Use a Confirmation to confirm the occurrence of an asynchronous event that you can’t check directly using an expectation. For more information, see Testing asynchronous code.
To validate that your code produces an expected value, use expect(_:_:sourceLocation:). This macro captures the expression you pass, and provides detailed information when the code doesn’t satisfy the expectation.
@Test func returningCustomerRemembersUsualOrder() throws {
let customer = try #require(Customer(id: 123))
// The test runner doesn't reach this line if the customer is nil.
#expect(customer.usualOrder.countOfItems == 2)
}
Tests allow developers to prove that the code they write is working as expected. If code isn’t working correctly, bug trackers are often used to track the work necessary to fix the underlying problem. It’s often useful to associate specific bugs with tests that reproduce them or verify they are fixed.
A bug’s URL is passed as a string and must be parseable according to RFC 3986. A bug’s unique identifier can be passed as an integer or as a string. For more information on the formats recognized by the testing library, see Interpreting bug identifiers.
A bug’s unique identifier or URL may be insufficient to uniquely and clearly identify a bug associated with a test. Bug trackers universally provide a “title” field for bugs that is not visible to the testing library. To add a bug’s title to a test, include it after the bug’s unique identifier or URL:
@Test(
"Food truck has napkins",
.bug(id: "12345", "Forgot to buy more napkins")
)
func hasNapkins() async {
...
}
Use this type to provide context or background information about a test’s purpose, explain how a complex test operates, or include details which may be helpful when diagnosing issues recorded by a test.
To add a comment to a test or suite, add a code comment before its @Test or @Suite attribute. See Adding comments to tests for more details.
Associate a time limit with tests by using timeLimit(_:).
If a test has more than one time limit associated with it, the value of this property is the shortest one. If a test has no time limits associated with it, the value of this property is nil.
Use this tag with members of the Tag type declared in an extension to mark them as usable with tests. For more information on declaring tags, see Adding tags to tests.
The value of this property is equal to the name of the symbol to which the Test attribute is applied (that is, the name of the type or function.) To get the customized display name specified as part of the Test attribute, use the displayName property.
If the value is true, then the testing library applies this trait recursively to child test suites and test functions. Otherwise, it only applies the trait to the test suite to which you added the trait.
By default, traits are not recursively applied to children.
The name of the source file is derived from this instance’s fileID property. It consists of the substring of the file ID after the last forward-slash character ( "/".) For example, if the value of this instance’s fileID property is "FoodTruck/WheelTests.swift", the file name is "WheelTests.swift".
The structure of file IDs is described in the documentation for #fileID in the Swift standard library.
Use this type to specify a test timeout with TimeLimitTrait. TimeLimitTrait uses this type instead of Swift’s built-in Duration type because the testing library doesn’t support high-precision, arbitrarily short durations for test timeouts. The smallest unit of time you can specify in a Duration is minutes.
Instances of Test attached to types rather than functions are test suites. They do not contain any test logic of their own, but they may have traits added to them that also apply to their subtests.
A test suite can be declared using the Suite(_:_:) macro.
The name of the module is derived from this instance’s fileID property. It consists of the substring of the file ID up to the first forward-slash character ( "/".) For example, if the value of this instance’s fileID property is "FoodTruck/WheelTests.swift", the module name is "FoodTruck".
The structure of file IDs is described in the documentation for the #fileID macro in the Swift standard library.
The testing library integrates with Swift concurrency, meaning that in many situations you can test asynchronous code using standard Swift features. Mark your test function as async and, in the function body, await any asynchronous interactions:
Call confirmation(_:expectedCount:isolation:sourceLocation:_:) in your asynchronous test function to create a Confirmation for the expected event. In the trailing closure parameter, call the code under test. Swift Testing passes a Confirmation as the parameter to the closure, which you call as a function in the event handler for the code under test when the event you’re testing for occurs:
@Test("OrderCalculator successfully calculates subtotal for no pizzas")
func subtotalForNoPizzas() async {
let calculator = OrderCalculator()
await confirmation() { confirmation in
calculator.successHandler = { _ in confirmation() }
_ = await calculator.subtotal(for: PizzaToppings(bases: []))
}
}
If you expect the event to happen more than once, set the expectedCount parameter to the number of expected occurrences. The test passes if the number of occurrences during the test matches the expected count, and fails otherwise.
@Test("Customers bought sandwiches")
func boughtSandwiches() async {
await confirmation(expectedCount: 0 ..< 1000) { boughtSandwich in
var foodTruck = FoodTruck()
foodTruck.orderHandler = { order in
if order.contains(.sandwich) {
boughtSandwich()
}
}
await FoodTruck.operate()
}
}
In this example, there may be zero customers or up to (but not including) 1,000 customers who order sandwiches. Any range expression which includes an explicit lower bound can be used:
Range Expression
Usage
1...
If an event must occur at least once
5...
If an event must occur at least five times
1 ... 5
If an event must occur at least once, but not more than five times
0 ..< 100
If an event may or may not occur, but must not occur more than 99 times
This preserves the list of the tags exactly as they were originally specified, in their original order, including duplicate entries. To access the complete, unique set of tags applied to a Test, see tags.
If the current task is running a test, or is a subtask of another task that is running a test, the value of this property describes the test’s currently-running case. If no test is currently running, the value of this property is nil.
If the current task is detached from a task that started running a test, or if the current thread was created without using Swift concurrency (e.g. by using Thread.detachNewThread(_:) or DispatchQueue.async(execute:)), the value of this property may be nil.
When you add this trait to a parameterized test function, that test runs its cases serially instead of in parallel. This trait has no effect when you apply it to a non-parameterized test function.
When you add this trait to a test suite, that suite runs its contained test functions (including their cases, when parameterized) and sub-suites serially instead of in parallel. If the sub-suites have children, they also run serially.
This trait does not affect the execution of a test relative to its peers or to unrelated tests. This trait has no effect if you disable test parallelization globally (for example, by passing --no-parallel to the swift test command.)
The default type is Never, which can’t be instantiated. The scopeProvider(for:testCase:)-cjmg method for any trait with Never as its test scope provider type must return nil, meaning that the trait doesn’t provide a custom scope for tests it’s applied to.
The default type is Never, which can’t be instantiated. The scopeProvider(for:testCase:)-cjmg method for any trait with Never as its test scope provider type must return nil, meaning that the trait doesn’t provide a custom scope for tests it’s applied to.
Test timeouts do not support high-precision, arbitrarily short durations due to variability in testing environments. You express the duration in minutes, with a minimum duration of one minute.
When you associate this trait with a test, that test must complete within a time limit of, at most, timeLimit. If the test runs longer, the testing library records a Issue.Kind.timeLimitExceeded(timeLimitComponents:) issue, which it treats as a test failure.
The testing library can use a shorter time limit than that specified by timeLimit if you configure it to enforce a maximum per-test limit. When you configure a maximum per-test limit, the time limit of the test this trait is applied to is the shorter of timeLimit and the maximum per-test limit. For information on configuring maximum per-test limits, consult the documentation for the tool you use to run your tests.
If a test is parameterized, this time limit is applied to each of its test cases individually. If a test has more than one time limit associated with it, the testing library uses the shortest time limit.
If the value is true, then the testing library applies this trait recursively to child test suites and test functions. Otherwise, it only applies the trait to the test suite to which you added the trait.
By default, traits are not recursively applied to children.
The testing library calls this method after it discovers all tests and their traits, and before it begins to run any tests. Use this method to prepare necessary internal state, or to determine whether the test should run.
The default implementation of this method does nothing.
The testing library calls this method after it discovers all tests and their traits, and before it begins to run any tests. Use this method to prepare necessary internal state, or to determine whether the test should run.
The default implementation of this method does nothing.
A value conforming to TestScopeProvider which you use to provide custom scoping for test or testCase. Returns nil if the trait doesn’t provide any custom scope for the test or test case.
If this trait’s type conforms to TestScoping, the default value returned by this method depends on the values of test and testCase:
If test represents a suite, this trait must conform to SuiteTrait. If the value of this suite trait’s isRecursive property is true, then this method returns nil, and the suite trait provides its custom scope once for each test function the test suite contains. If the value of isRecursive is false, this method returns self, and the suite trait provides its custom scope once for the entire test suite.
If test represents a test function, this trait also conforms to TestTrait. If testCase is nil, this method returns nil; otherwise, it returns self. This means that by default, a trait which is applied to or inherited by a test function provides its custom scope once for each of that function’s cases.
A trait may override this method to further customize the default behaviors above. For example, if a trait needs to provide custom test scope both once per-suite and once per-test function in that suite, it implements the method to return a non- nil scope provider under those conditions.
A trait may also implement this method and return nil if it determines that it does not need to provide a custom scope for a particular test at runtime, even if the test has the trait applied. This can improve performance and make diagnostics clearer by avoiding an unnecessary call to provideScope(for:testCase:performing:).
If this trait’s type does not conform to TestScoping and its associated TestScopeProvider type is the default Never, then this method returns nil by default. This means that instances of this trait don’t provide a custom scope for tests to which they’re applied.
Implement this method to conform to the Hashable protocol. The components used for hashing must be the same as the components compared in your type’s == operator implementation. Call hasher.combine(_:) with each of these components.
This function is the only requirement of the Comparable protocol. The remainder of the relational operator functions are implemented by the standard library for any type that conforms to Comparable.
By default, tests run in parallel with respect to each other. Parallelization is accomplished by the testing library using task groups, and tests generally all run in the same process. The number of tests that run concurrently is controlled by the Swift runtime.
Parallelization can be disabled on a per-function or per-suite basis using the serialized trait:
@Test(.serialized, arguments: Food.allCases) func prepare(food: Food) {
// This function will be invoked serially, once per food, because it has the
// .serialized trait.
}
@Suite(.serialized) struct FoodTruckTests {
@Test(arguments: Condiment.allCases) func refill(condiment: Condiment) {
// This function will be invoked serially, once per condiment, because the
// containing suite has the .serialized trait.
}
@Test func startEngine() async throws {
// This function will not run while refill(condiment:) is running. One test
// must end before the other will start.
}
}
When added to a parameterized test function, this trait causes that test to run its cases serially instead of in parallel. When applied to a non-parameterized test function, this trait has no effect. When applied to a test suite, this trait causes that suite to run its contained test functions and sub-suites serially instead of in parallel.
This trait is recursively applied: if it is applied to a suite, any parameterized tests or test suites contained in that suite are also serialized (as are any tests contained in those suites, and so on.)
This trait doesn’t affect the execution of a test relative to its peers or to unrelated tests. This trait has no effect if test parallelization is globally disabled (by, for example, passing --no-parallel to the swift test command.)
The customized display name of this test. If the value of this argument is nil, the display name of the test is derived from the associated function’s name.
traits
Zero or more traits to apply to this test.
zippedCollections
Two zipped collections of values to pass to testFunction.
Because all errors thrown by body are caught as known issues, this function is not throwing. If only some errors or issues are known to occur while others should continue to cause test failures, use withKnownIssue(_:isIntermittent:sourceLocation:_:when:matching:) instead.
An optional comment to apply to any issues generated by this function.
expectedCount
The number of times the expected event should occur when body is invoked. The default value of this argument is 1, indicating that the event should occur exactly once. Pass 0 if the event should never occur when body is invoked.
sourceLocation
The source location to which any recorded issues should be attributed.
Use confirmations to check that an event occurs while a test is running in complex scenarios where #expect() and #require() are insufficient. For example, a confirmation may be useful when an expected event occurs:
In a context that cannot be awaited by the calling function such as an event handler or delegate callback;
More than once, or never; or
As a callback that is invoked as part of a larger operation.
To use a confirmation, pass a closure containing the work to be performed. The testing library will then pass an instance of Confirmation to the closure. Every time the event in question occurs, the closure should call the confirmation:
let n = 10
await confirmation("Baked buns", expectedCount: n) { bunBaked in
foodTruck.eventHandler = { event in
if event == .baked(.cinnamonBun) {
bunBaked()
}
}
await foodTruck.bake(.cinnamonBun, count: n)
}
When the closure returns, the testing library checks if the confirmation’s preconditions have been met, and records an issue if they have not.
A closure that contains the trait’s custom condition logic. If this closure returns false, the trait allows the test to run. Otherwise, the testing library skips the test.
A closure that contains the trait’s custom condition logic. If this closure returns false, the trait allows the test to run. Otherwise, the testing library skips the test.
A closure that contains the trait’s custom condition logic. If this closure returns true, the trait allows the test to run. Otherwise, the testing library skips the test.
The customized display name of this test. If the value of this argument is nil, the display name of the test is derived from the associated function’s name.
traits
Zero or more traits to apply to this test.
collection
A collection of values to pass to the associated test function.
If condition evaluates to false, an Issue is recorded for the test that is running in the current task and an instance of ExpectationFailedError is thrown.
A closure that contains the trait’s custom condition logic. If this closure returns true, the trait allows the test to run. Otherwise, the testing library skips the test.
Whether or not the known issue occurs intermittently. If this argument is true and the known issue does not occur, no secondary issue is recorded.
sourceLocation
The source location to which any recorded issues should be attributed.
body
The function to invoke.
precondition
A function that determines if issues are known to occur during the execution of body. If this function returns true, encountered issues that are matched by issueMatcher are considered to be known issues; if this function returns false, issueMatcher is not called and they are treated as unknown.
issueMatcher
A function to invoke when an issue occurs that is used to determine if the issue is known to occur. By default, all issues match.
Use this function when a test is known to raise one or more issues that should not cause the test to fail, or if a precondition affects whether issues are known to occur. For example:
It is not necessary to specify both precondition and issueMatcher if only one is relevant. If all errors and issues should be considered known issues, use withKnownIssue(_:isIntermittent:sourceLocation:_:) instead.
The customized display name of this test. If the value of this argument is nil, the display name of the test is derived from the associated function’s name.
traits
Zero or more traits to apply to this test.
collection1
A collection of values to pass to testFunction.
collection2
A second collection of values to pass to testFunction.
The customized display name of this test. If the value of this argument is nil, the display name of the test is derived from the associated function’s name.
See also my blog post: https://steipete.me/posts/2025/migrating-700-tests-to-swift-testing