Swift Testing Attachments in Xcode 26 transform test debugging from guesswork into precise diagnosis. Let's explore how this game-changing feature works.
The Old Way: Print Statement Hell
Picture this familiar scenario:
@Test
func userProfileTest() async throws {
let user = try await apiClient.fetchUser(id: "12345")
#expect(user.name == "John Doe")
#expect(user.isActive == true)
}
When this fails, you get:
β Expected true, got false at line 4
What you actually need to know:
- What did the API return?
- Was the response malformed?
- What was the network status?
- What went wrong?
Before attachments, debugging meant adding print statements, re-running tests, digging through console output, and repeating this cycle endlessly.
The New Way: Rich Context Automatically
Swift Testing Attachments let you capture any data during test execution and attach it to test results. When tests fail, you get complete context about what actually happened.
Basic Concept
Think of attachments as "evidence" that gets automatically collected and stored with your test results. When something goes wrong, you can immediately see exactly what happened.
import Testing
@Test
func userProfileWithContext() async throws {
let user = try await apiClient.fetchUser(id: "12345")
// Attach the API response for debugging
Attachment.record(user, named: "User API Response")
#expect(user.name == "John Doe")
#expect(user.isActive == true)
}
Now when the test fails, you can see exactly what the API returned!
Real-World Examples
π Network Request Debugging
The most common use case - understanding what your API actually returned:
@Test
func orderCreation() async throws {
let orderRequest = OrderRequest(
items: [Item(id: "abc", quantity: 2)],
shippingAddress: testAddress
)
// Attach request and response for debugging
Attachment.record(orderRequest, named: "Order Request")
let response = try await orderService.createOrder(orderRequest)
Attachment.record(response, named: "Order Response")
// If there's an error, attach details
if let error = response.error {
let errorDetails = """
Status: \(error.statusCode)
Message: \(error.message)
Time: \(Date())
"""
Attachment.record(errorDetails, named: "Error Details")
}
#expect(response.orderId != nil)
}
π± UI Testing with Screenshots
Perfect for visual testing where you need to see what actually happened:
@Test
func loginFlow() throws {
let app = XCUIApplication()
app.launch()
// Capture initial state
let screenshot = app.screenshot()
Attachment.record(screenshot.pngRepresentation, named: "Initial State")
app.buttons["Login"].tap()
// Capture after login attempt
let loginScreenshot = app.screenshot()
Attachment.record(loginScreenshot.pngRepresentation, named: "After Login")
#expect(app.staticTexts["Welcome"].exists)
}
ποΈ Database State Debugging
Understand exactly what's in your database when tests fail:
@Test
func userDeletion() async throws {
let user = try await createTestUser(email: "test@example.com")
// Capture before and after states
let beforeUsers = try await database.fetchAllUsers()
Attachment.record(beforeUsers, named: "Users Before Deletion")
try await userService.deleteUser(user.id)
let afterUsers = try await database.fetchAllUsers()
Attachment.record(afterUsers, named: "Users After Deletion")
#expect(!afterUsers.contains { $0.id == user.id })
}
Smart Attachment Strategies
Only Attach What You Need
Attachments can consume storage, so be strategic:
@Test
func apiResponseTest() async throws {
let response = try await apiClient.fetchLargeDataSet()
// β Don't attach massive data unnecessarily
// Attachment.record(response.fullDataSet, named: "Complete Dataset") // Could be 100MB!
// β
Attach summary instead
let summary = DataSummary(
count: response.fullDataSet.count,
firstItem: response.fullDataSet.first,
lastItem: response.fullDataSet.last
)
Attachment.record(summary, named: "Dataset Summary")
// β
Only attach full data if something's wrong
if response.fullDataSet.isEmpty {
Attachment.record(response.fullDataSet, named: "Empty Dataset Debug")
}
}
Conditional Attachments
Only collect expensive debug data when needed:
@Test
func complexOperation() async throws {
let result = try await performComplexOperation()
// Only attach expensive debug data if confidence is low
if result.confidence < 0.8 {
let debugData = try await gatherDetailedDiagnostics()
Attachment.record(debugData, named: "Low Confidence Debug")
}
#expect(result.success)
}
What Can You Attach?
Swift Testing supports attaching:
- Strings - Error messages, logs, debug output
- Data - Raw bytes, JSON responses, file contents
- Any Codable object - Your custom structs, API responses
- Files - Screenshots, generated reports, log files
// String attachment
Attachment.record("Debug info: \(debugMessage)", named: "Debug Log")
// Custom object attachment
struct TestEnvironment: Codable {
let device: String
let osVersion: String
let timestamp: Date
}
let environment = TestEnvironment(
device: UIDevice.current.model,
osVersion: UIDevice.current.systemVersion,
timestamp: Date()
)
Attachment.record(environment, named: "Test Environment")
Rich Context
// NEW: Rich debugging context
@Test
func dataProcessing() throws {
let input = generateTestData()
Attachment.record(input, named: "Input Data")
let result = processor.process(input)
Attachment.record(result, named: "Processing Result")
// Attach processing stats
let stats = ProcessingStats(
inputSize: input.count,
outputSize: result.count,
processingTime: processor.lastProcessingTime
)
Attachment.record(stats, named: "Performance Stats")
#expect(result.isValid)
}
Getting Started
Requirements:
- Xcode 26 (includes Swift 6.2)
- macOS 15.6 or later (for Xcode 26)
Setup:
- Install Xcode 26 from the Mac App Store or Apple Developer portal
- Import the Testing framework:
import Testing
- Use
@Test
instead of XCTest'sfunc testSomething()
- Replace
XCTAssert
with#expect
- Add attachments with
Attachment.record()
The Bottom Line
- Rich context automatically captured
- Visual debugging with actual data
- Complete understanding of failures
- One-click access to all relevant information
Your test failures become informative. Every attachment tells a story about what actually happened during test execution.
Swift Testing Attachments are available in Swift 6.2, which requires Xcode 26. Xcode 26 includes Swift 6.2 and is required to access these new testing features. They work on all platforms that support Swift Testing.
Top comments (1)
Attachments are included in test reports in Xcode or written to disk when tests are run at the command line. To create an attachment, you need a value of some type that conforms to Attachable.