Migration from XCTest
Migration from XCTest
Section titled “Migration from XCTest”When to use this reference
Section titled “When to use this reference”Use this file for incremental migration of existing XCTest code to Swift Testing while preserving safety and CI signal.
Coexistence strategy
Section titled “Coexistence strategy”- Swift Testing and XCTest can coexist in the same target.
- Migrate incrementally; do not block migration on full rewrite.
- A single source file can import both
XCTestandTestingduring migration. - Keep XCTest where Swift Testing does not apply:
- UI automation (
XCUIApplication) - performance APIs (
XCTMetric) - Objective-C-only tests
- UI automation (
Mixed import file example
Section titled “Mixed import file example”import XCTestimport TestingPractical migration order
Section titled “Practical migration order”- Convert assertions to
#expect/#require. - Replace
test...naming constraints with explicit@Test. - Reorganize classes into suites where helpful.
- Collapse repetitive methods into parameterized tests.
- Add traits/tags for control and test-plan filtering.
Example conversion: class method -> Swift Testing function
Section titled “Example conversion: class method -> Swift Testing function”// Before (XCTest)final class PriceTests: XCTestCase { func testDiscountedTotal() { XCTAssertEqual(Price.total(subtotal: 20, discount: 5), 15) }}
// After (Swift Testing)import Testing
@Test func discountedTotal() { #expect(Price.total(subtotal: 20, discount: 5) == 15)}Assertion mapping highlights
Section titled “Assertion mapping highlights”- Most
XCTAssert*variants ->#expect(...). - Optional unwrap checks ->
try #require(optionalValue). - Early-stop semantics ->
#requireinstead of globalcontinueAfterFailure = false. XCTFail("...")->Issue.record("...").
Table-style quick mappings
Section titled “Table-style quick mappings”// XCTAssertTrue(isEnabled)#expect(isEnabled)
// XCTAssertNil(error)#expect(error == nil)
// XCTAssertThrowsError(try run())#expect(throws: (any Error).self) { try run() }
// try XCTUnwrap(user)let user = try #require(user)Suite model differences
Section titled “Suite model differences”- XCTest: class +
XCTestCase. - Swift Testing: struct/actor/class suites, explicit attributes, value-semantics-friendly defaults.
- Setup can move from
setUppatterns to suite init when appropriate. - Teardown can move to
deinitwhen using class/actor suites. - XCTest sync tests default to main actor behavior; Swift Testing runs tests on arbitrary tasks unless explicitly isolated (e.g.
@MainActor).
Setup migration example
Section titled “Setup migration example”import Testing
struct SessionTests { let session: Session
init() { self.session = Session(environment: .test) }
@Test func startsDisconnected() { #expect(session.isConnected == false) }}Async migration specifics
Section titled “Async migration specifics”- Prefer
awaitdirectly for async APIs. - Convert completion-handler APIs with
withCheckedContinuation/withCheckedThrowingContinuation. - Replace
XCTestExpectationpatterns with confirmations when testing asynchronous event streams.
Expectation-style flow -> confirmation
Section titled “Expectation-style flow -> confirmation”import Testing
@Test func receivesAtLeastOneEvent() async { await confirmation("Receives event", expectedCount: 1...) { confirm in confirm() }}Migration hygiene
Section titled “Migration hygiene”- Prefer mechanical, reviewable commits.
- Use editor pattern-replace to accelerate common assertion conversions.
- Avoid mixing XCTest assertions in Swift Testing tests (and vice versa).
Common pitfalls
Section titled “Common pitfalls”- Migrating all files at once instead of phased migration.
- Keeping
continueAfterFailurepatterns instead of targeted#require. - Marking every migrated test
@MainActorunnecessarily.