TL;DR: In the Swift Testing framework, #expect is a “soft assertion” (the test continues even if it fails), while #require is a “hard assertion” (the test stops immediately upon failure). Additionally, #require serves as the modern replacement for XCTUnwrap, allowing you to safely unwrap optional values within your tests.
#expect and #require are the two foundational macros in the Swift Testing framework (the standard testing library in Swift 6). While they both serve to verify conditions, they differ fundamentally in their error handling mechanisms and intended use cases.
1. Syntax: The Necessity of try
The mechanism behind #require works by throwing an error to halt test execution. Therefore, regardless of whether the expression inside it throws an error itself, you must use the try keyword when calling it.
@Test func syntaxDemo() throws {
let value = 10
// ✅ Correct: #expect does not need 'try' (unless the expression itself throws)
#expect(value == 10)
// ✅ Correct: #require must always be used with 'try'
try #require(value > 0)
}
2. Core Feature: Unwrapping Optionals
#require is the perfect modern replacement for XCTest’s XCTUnwrap. It attempts to unwrap an optional value. If the value is nil, the test fails and stops immediately; if successful, it returns the unwrapped non-optional value for subsequent use.
@Test func unwrappingDemo() throws {
let user: User? = fetchUser()
// If user is nil, the test terminates here.
// Equivalent to: guard let user = user else { throw Failure }
let validUser = try #require(user)
// Here, validUser is a non-optional type
#expect(validUser.name == "Fatbobman")
}
3. Runtime Behavior: Continue vs. Stop
This is the most significant logical difference, determining how issues are reported.
#expect (Soft Assertion)
Even if the assertion fails, the test function continues execution. This allows you to collect multiple error messages in a single test run.
@Test func softAssertion() {
#expect(1 == 2) // ❌ Fails, error is recorded
print("This line will be executed")
#expect(3 == 4) // ❌ Fails again, second error is recorded
}
Result: The test is marked as failed, and the report shows 2 issues.
#require (Hard Assertion)
Once the assertion fails, an error is thrown, and the test function terminates immediately. This is ideal for scenarios where subsequent code depends on the current condition (like precondition checks).
@Test func hardAssertion() throws {
try #require(1 == 2) // ❌ Fails, throws error, and terminates
print("This line will never be executed")
}
Result: The test is marked as failed, and the report shows only 1 issue.
Summary: Which One to Choose?
| Feature | #expect | #require |
|---|---|---|
| Assertion Type | Soft Assertion | Hard Assertion |
| Failure Behavior | Records error, continues execution | Records error, terminates immediately |
| Keyword | Usually doesn’t need try | Must use try |
| Return Value | None | Returns unwrapped value (for Optionals) |
| Use Case | Value verification, state checks | Preconditions, Optional unwrapping |