Four interoperability modes define the choice: start by evaluating complete for a gradual migration because it preserves the original severity of cross-framework issues. First verify the setting in each Test Plan; an older plan may still use limited. Choose strict when you intend to block specific cross-framework calls, and reserve none for temporary diagnosis because it can hide useful issues. Apple’s Xcode 27 release notes and Swift Testing migration guidance describe the relevant behavior.
This guide is for developers maintaining existing XCTest suites while introducing Swift Testing, engineers responsible for Xcode Test Plans and CI, and technical leads setting migration rules.
If you only need an introduction to writing tests in either framework, this is not a framework tutorial. It focuses on interoperability settings and how to verify them.
Last updated October 7, 2026. Behavior and settings checked against Apple’s Xcode 27 release notes, Swift Testing migration documentation, and WWDC26 migration session.
01 Scope of interoperability in a mixed test suite
Swift Testing and XCTest can coexist in a test target. Interoperability is not a command to replace one framework with the other. It governs how Xcode handles issues when a test written with one framework uses APIs from the other.
Consider a Swift Testing test that calls an XCTest assertion:
import Testing
import XCTest
@Test
func checksLegacyAssertion() {
XCTAssertEqual(2 + 2, 5)
}
The assertion is deliberately false, so this example gives you a concrete event to observe. The decision is not whether XCTest assertions exist in the codebase. It is how the selected interoperability mode handles cross-framework behavior and makes the resulting issue visible. Apple documents Swift Testing’s issue-recording API in Issue.record; consult that documentation alongside the migration guidance when you interpret results.
Can Swift Testing and XCTest run in the same test package? Yes. You can maintain tests from both frameworks in the same package or test target. Verify the interoperability setting that applies to the specific test execution rather than assuming that coexistence means all cross-framework calls will be handled identically.
Apple’s Xcode testing overview describes the broader testing workflow, while its XCTest framework documentation defines the XCTest framework. Those scopes matter when you decide what to migrate: you can introduce Swift Testing without first rewriting every existing XCTest test.
For a gradual migration, separate these decisions:
- Framework choice: which framework new or revised tests will use.
- Interoperability policy: how mixed-framework calls and issues are handled.
- Migration scope: which existing tests, if any, you will convert now.
- Execution scope: which Test Plan, package toolchain, and CI entry point actually run the tests.
Keeping those decisions separate prevents a common mistake: treating a change in interoperability mode as if it had converted the test suite or repaired its assertions. It does neither. It changes how a class of cross-framework behavior is handled.
02 Severity handling across the four modes
Compare the modes by the policy you need, not by which setting produces the cleanest-looking test log. Apple’s Xcode 27 release notes are the reference for the mode definitions. Use the list below as a decision aid, then verify the observed result against your installed Xcode and test configuration.
limited: compatibility-oriented handling. Use it when you need to preserve the behavior of an existing plan while you investigate or stage a migration. Do not infer that an older plan is currently using it just because it was created with an earlier Xcode release; inspect the plan itself.complete: preserve original severity. Evaluate this first for a gradual migration. The key property is that cross-framework issues retain their original severity instead of being silently treated as a harmless compatibility detail. Confirm how that severity appears in your test results.strict: block selected cross-framework calls. Choose it when your team has decided that particular calls must not cross the framework boundary. It is a policy control, not a general-purpose way to make a mixed suite more reliable. Identify the calls you intend to prohibit and test that policy with a small example.none: disable interoperability handling. Use it only as a temporary diagnostic comparison when you have a specific reason to isolate behavior. If you keep it as a routine workaround, you may hide an issue that should be investigated.
Should you choose limited or complete? For incremental migration, begin by evaluating complete. Keep limited only when you have confirmed that its compatibility behavior is necessary for the suite or plan you are maintaining. Record the reason and expected follow-up; do not select a mode only because it makes a failing CI job appear green.
A disciplined decision ties the mode to an observable result. Run the same test that invokes an XCTest assertion from a Swift Testing test. Check whether the issue is recorded, whether its severity matches your team’s expectation, and whether the test outcome is consistent with your policy. This is more useful than comparing setting names without running a representative case.
Treat
noneas a diagnostic control, not a migration destination. If the result changes undernone, preserve that difference as evidence and restore the intended policy before accepting the change.
The WWDC26 migration demonstration can help you compare the documented behavior with the workflow shown by Apple. Do not treat a result from the demonstration as a measurement of your own project. Your suite, plan, and toolchain determine what you need to validate.
03 Test Plan age and effective setting
An Xcode version alone does not tell you which interoperability mode a test run uses. A project opened in Xcode 27 may contain a Test Plan created earlier, and that plan may retain a different setting from one created in a newer project. That is why “we upgraded Xcode” is not an adequate configuration check.
Why can an older Test Plan remain on limited after an Xcode upgrade? The plan’s existing configuration may persist across the upgrade. Check the actual plan rather than assuming Xcode rewrote it. Apple’s Test Plan organization guidance covers the plan-level configuration context; compare it with the Xcode 27 release notes before deciding that a setting is a toolchain-wide default.
Use this review path:
- Open the scheme and identify the Test Plan used for the run.
- Inspect that plan’s Swift Testing and XCTest interoperability setting.
- Repeat the inspection for any other plan used for local testing or CI.
- Record the selected mode alongside the plan name in your team’s migration notes.
- Run the representative mixed-framework test under the plan you intend to keep.
- Check whether the result matches the policy you documented.
Do not rely on a screenshot from a developer’s local scheme as proof of the CI configuration. A shared scheme can refer to a different plan, and CI can invoke a different scheme or test plan from the one a developer selects in Xcode. The setting that matters is the one applied to the test invocation you are accepting.
When you change an existing plan, save the change intentionally and rerun the same test. If the result changes, capture the before-and-after configuration and test output. That evidence makes it possible to distinguish a policy change from a source-code fix.
04 Swift Package and Xcode execution differences
Swift Package and Xcode project workflows are related, but you should not assume that their defaults match. The applicable behavior can depend on the toolchain and the package’s swift-tools-version, as well as on the Xcode Test Plan used for an Xcode project. Apple’s migration documentation and Xcode 27 release notes are the sources to check for documented defaults and changes.
Do Swift Package and Xcode projects use the same default mode? Do not assume they do. Check the package’s declared tools version and the actual Xcode Test Plan setting, then compare results using the same cross-framework test. Avoid copying a default observed in one project into every package or CI job.
To isolate the difference, keep the test case and source revision constant. Run it through the package workflow and through the Xcode workflow. Then compare:
- which toolchain each invocation uses;
- which package tools version is declared;
- which Test Plan, if any, the Xcode invocation selects;
- whether the cross-framework issue appears in both results;
- whether its severity and test outcome match your intended policy.
For Xcode command-line testing, follow Apple’s running tests and interpreting results guide. Record the exact command or CI step used, along with the selected scheme and plan. For package testing, record the command, toolchain, and manifest tools version. This makes the comparison reproducible without assuming that the same interface or setting controls every execution path.
The keyword Swift 6.4 is relevant when you are tracking the compiler and package-toolchain context. Treat it as a version to verify in the environment, not as proof that all projects share one interoperability default. If your local Xcode and remote runner do not use the same toolchain, validate both rather than generalizing from the version string alone.
05 Acceptance signals for migration and remote CI
The migration decision should pass on observable evidence. A suite that runs successfully is not automatically configured correctly: a suppressed issue can make a job look healthy while removing information your team needs. Conversely, a visible cross-framework issue may be expected during staged migration and need a policy decision rather than an immediate rewrite.
Use this checklist with one mixed-framework test and one representative project plan:
- [ ] Confirm that the test target contains the intended Swift Testing and XCTest tests.
- [ ] Record the interoperability mode in every Test Plan used for acceptance.
- [ ] Verify that a Swift Testing test calling an XCTest assertion produces the expected issue.
- [ ] Confirm that the issue’s severity matches the chosen policy.
- [ ] Run the same source revision through the package workflow, if the project uses Swift Package testing.
- [ ] Run it through the Xcode Test Plan used in CI.
- [ ] Compare the local Xcode, local command-line, and remote CI results.
- [ ] Confirm that existing XCTest tests still run under the selected configuration.
- [ ] If results differ, identify the toolchain, manifest, plan, or invocation difference before changing the mode.
- [ ] Save the configuration and test output as migration evidence.
For a remote Mac CI node, check the execution path rather than merely checking that a Mac is available. Compare the Xcode version, selected scheme, Test Plan, package tools version, and command used by the job with the local acceptance run. Apple’s command-line test guidance is useful when you need to make the invocation itself explicit.
If your team still has different outcomes, avoid changing several variables at once. First reproduce the result with the same source and plan. Then vary one execution detail, such as the toolchain or invocation, and record whether the issue’s visibility or severity changes. That gives you a useful diagnostic trail without turning none into a blanket workaround.
A CI pass is meaningful only when the runner used the intended plan and policy. Keep the configuration evidence next to the test result, especially when local and remote runs select different execution paths.
06 Choosing a mode and moving to a repeatable Mac environment
For most teams introducing Swift Testing gradually, evaluate complete first, then confirm the behavior in each older Test Plan and relevant package workflow. Use strict when you deliberately want to block defined cross-framework calls. Retain limited only when its compatibility behavior is intentional. Use none briefly to diagnose a specific discrepancy, then restore the policy that keeps relevant issues visible.
Before closing the migration task, make the choice explicit in the repository or team runbook. State which plan and toolchain were checked, which mixed-framework case you ran, and what issue visibility and severity you expected. If a later Xcode update changes documented behavior, rerun that evidence rather than relying on a past result.
A local-only CI setup can make this verification harder when the Mac is shared, unavailable to the job, or configured differently from the developer’s machine. A Linux or Windows runner also cannot replace the Mac execution path for tests that require Apple’s Xcode toolchain. A remote Mac gives you another way to run that native test workflow without purchasing a separate machine, but it does not remove the need to pin and verify the toolchain, Test Plan, and invocation.
If you lack a Mac environment where you can run the same Xcode tests used in CI, CALMVPS offers remote access to a real Mac for development and testing. Review the CALMVPS remote Mac service and its plan and pricing details to decide whether a temporary or ongoing remote environment fits your test schedule. If your team already has a stable Mac for long-running workloads or needs local physical-device connections, keeping that setup may be the better choice. Either way, first confirm the interoperability mode in the existing Test Plan, then compare the same mixed test locally and in CI before accepting the migration.