Xcode 27 introduces enhanced interoperability modes (limited, complete, strict) between Swift Testing and XCTest, making it safer and more incremental to migrate test suites from XCTest to Swift Testing. New tools like Test.cancel, updated cross-framework issue reporting, and Coding Assistant migration skills lower the barrier to adoption.
โข Xcode 27 adds complete and strict interoperability modes on top of the existing limited mode introduced in Xcode 16
โข New SWIFT_TESTING_XCTEST_INTEROP_MODE environment variable lets SPM users override the default interoperability mode at build time
โข swift-tools-version 6.4 now defaults Swift packages to complete mode, elevating cross-framework issues to errors automatically
โข Xcode Coding Assistant gains a dedicated migration skill that can review and partially automate XCTest-to-Swift-Testing conversion
โข New interoperability modes (complete, strict) in Xcode 27 allow teams to enforce that XCTest helper code is replaced with Swift Testing equivalents, catching cross-framework issues as errors or fatal stops rather than silent warnings
โข Test plans created before Xcode 27 inherit limited mode automatically, while new projects default to complete mode โ meaning teams need to be aware of these defaults to avoid unexpected test behavior
โข Test.cancel and the enabled/disabled traits let you replace XCTSkip and continueAfterFailure with more expressive, Swift-native patterns without breaking existing XCTest targets
Shows how an existing XCTFail-based helper can be migrated to Issue.record so it works correctly in both XCTest and Swift Testing tests, leveraging the complete interoperability mode introduced in Xcode 27.
โ// FruitTests.swift (pre-iOS 27 / XCTest approach)โimport XCTest+// FruitTests.swift+import Testing+import Foundationโ// Old helper using XCTFail โ only works correctly in XCTest contextโfunc assertUnique(_ names: [String], file: StaticString = #file, line: UInt = #line) {+// Shared helper โ migrated from XCTFail to Issue.record+// so it works in both XCTest and Swift Testing contexts+func assertUnique(_ names: [String], sourceLocation: SourceLocation = #_sourceLocation) {var seen = Set<String>()for name in names {if seen.contains(name) {โ XCTFail("Duplicate name found: \(name)", file: file, line: line)+ Issue.record(+ "Duplicate name found: \(name)",+ sourceLocation: sourceLocation+ )}seen.insert(name)}}struct Fruit {let name: Stringlet climate: String}โfinal class FruitTests: XCTestCase {+// Swift Testing suite+struct FruitTests {let fruits: [Fruit] = [Fruit(name: "Mango", climate: "tropical"),Fruit(name: "Apple", climate: "temperate"),Fruit(name: "Lychee", climate: "tropical")]โ func testTropicalFruits() {+ @Test("Fruits have tropical climate")+ func tropicalFruits() {let tropical = fruits.filter { $0.climate == "tropical" }โ XCTAssertFalse(tropical.isEmpty)+ #expect(!tropical.isEmpty)}โ func testUniqueFruitNames() {โ // continueAfterFailure = false halts on first XCT assertion failureโ continueAfterFailure = false+ @Test("All fruit names are unique")+ func uniqueFruitNames() {+ // #require halts the test on first failure โ replaces continueAfterFailure = falselet names = fruits.map(\.name)โ XCTAssertGreaterThan(names.count, 0)โ assertUnique(names)+ #expect(names.count > 0)+ assertUnique(names) // Issue.record correctly surfaces as a Swift Testing failure}โ func testPending() throws {โ // Runtime skip via XCTSkipโ throw XCTSkip("Pending backend data")+ @Test("Skip example using disabled trait", .disabled("Pending backend data"))+ func pendingTest() {+ // Replaces XCTSkip โ no runtime logic needed in the body+ #expect(Bool(true))}}
Cross-framework interoperability mode affects whether XCTFail called inside a Swift Testing test is a warning, error, or fatal stop. Test plans created before Xcode 27 default to limited mode (warnings only), which can mask real failures. Swift Package projects default to limited mode with swift-tools-version < 6.4; bump to 6.4 for complete mode. UI automation, performance tests, and ObjC exception tests must remain in XCTest.
None โ applies to all simulators and devices supported by Xcode 27
More iOS 27 APIs land every week.
Get notified when new capabilities are published โ no noise, just signal.