A reused XCTest assertion helper runs inside a Swift Testing test, but the result looks successful when it should fail.

Fastest check: Trace which framework owns the assertion, then verify the Swift toolchain, the package’s swift-tools-version, and the interoperability mode before rewriting tests.

This guide is for students combining an older XCTest course project with newer Swift Testing tests.
It also helps beginners checking a toolchain or package upgrade, and learners whose Xcode test report does not match what they expected.

Updated October 10, 2026; verified against the Swift 6.4 release notes and Apple’s migration and testing documentation. Swift 6.4 release notes · Apple’s migration guidance

Swift 6.4 XCTest interoperability errors: start with the failing symptom

Swift 6.4 supports gradual interoperability between XCTest and Swift Testing. That gives a student a path to use supported features from both frameworks in a project without immediately converting every old test. It does not make every API, test lifecycle, or result-reporting behavior interchangeable. The release and migration documentation describe the support and its boundaries.

Start by sorting the symptom into one of three groups:

  • The test appears to pass, but an assertion should have failed. Find out which framework’s assertion actually ran and inspect the test result for an interoperability diagnostic.
  • A warning appears, but tests still run. Record the warning and check the toolchain and package settings before treating it as proof that the test is broken.
  • The test fails, is skipped, or never appears in the results. Check whether the intended test target built and ran before assuming that assertion interoperability caused the problem.

For this investigation, the toolchain means the Swift compiler and related tools used to run the test. The package’s swift-tools-version works more like a package ruleset: it declares the Swift tools version that the package uses. Interoperability mode is the policy that controls how cross-framework issues are handled and reported. These are separate checks, so changing one does not automatically confirm the others. (PackageDescription documentation for swift-tools-version)

Trace the assertion to its owning framework

A common student setup is to keep an older helper function and call it from a newer Swift Testing test. The test body may look short and harmless, while the helper contains the assertion that determines whether the test passes. Read both pieces of code.

For example, the test can call a helper that contains XCTAssertEqual. The visible test uses Swift Testing, but the assertion is still an XCTest assertion. In the reverse direction, an XCTest test can use #expect, which is a Swift Testing macro. Swift 6.4 documents support for these interoperability directions, but that support should not be taken to mean that every surrounding test behavior is the same. (Swift interoperability proposal ST-0021)

Use this low-risk trace before editing the test:

  • [ ] Find the test function that Xcode ran.
  • [ ] Follow any helper calls until you reach the assertion or macro.
  • [ ] Label the test and assertion separately: XCTest test, Swift Testing test, XCTest assertion, or Swift Testing macro.
  • [ ] Check the test report for a diagnostic about using one framework from the other.
  • [ ] Keep the original helper unchanged until you know whether the problem is reporting, discovery, or build status.

If the test is found and executed, but the expected failure is not reported clearly, continue to the interoperability-mode and configuration checks. If the test never appears in the results, pause here: that symptom points first to test discovery, target selection, or build status rather than to an assertion that ran silently. Apple’s guidance explains how to run tests and interpret their results. (Running tests and interpreting results)

Remember: “The test passed” and “the intended assertion ran and reported correctly” are different claims. Confirm both from the test result and the code path.

Check the mode and package rules before changing code

Interoperability mode names include none, limited, complete, and strict. They describe different boundaries for cross-framework behavior and reporting; they are not interchangeable labels for “off,” “normal,” or “best.” Use the official migration guidance and the Swift proposal to determine what a mode means for the project’s toolchain and package setup. Avoid choosing a more permissive mode just to make a red result disappear. (Apple’s migration guidance)

The package’s swift-tools-version matters because it participates in how Swift Package Manager interprets the package. A package that has not changed its declared tools version may not behave like a newly configured package, even when a newer Swift toolchain is installed. Check the value in the package manifest and compare it with the toolchain actually used by the test run. Don’t infer either value from the version of Xcode that happens to be open.

What you observe First low-risk check What the result suggests
The test runs, but an XCTest assertion inside it does not report as expected Trace the helper and inspect the report for an interoperability diagnostic Check the applicable mode and documented cross-framework behavior before replacing the helper
A warning appears while the test continues Save the exact warning and record the toolchain and package tools version Treat the warning as a clue; it does not alone prove that the test result is invalid
The expected test is missing or marked as skipped Confirm the selected test target, discovery, skip state, and build result Investigate test execution or project configuration before changing interoperability
Changing a mode changes the severity of the result Record the previous and current mode, then compare their documented behavior A different severity can reflect reporting policy, not a repaired assertion

If a package or test target uses an interoperability setting, identify where it is applied and which target it affects. The Swift proposal documents the interoperability design and configuration; follow its exact spelling and scope rather than guessing at a setting name or adding an unverified Xcode interface step.

Decision branches

  • If the test runs, the assertion crosses framework boundaries, and the report names an interoperability issue, check the documented mode and package configuration before changing the test.
  • If the test is absent, skipped, or blocked by a build failure, investigate the test target and project first; an assertion that did not run cannot explain the report.
  • If the mode change only alters the warning or error level, compare the documented behavior and keep the stricter setting unless the project has a clear, supported reason to change it.
  • If a minimal test works but the course project does not, compare the project’s toolchain, swift-tools-version, target, and test report rather than replacing all its assertions.

Separate assertion reporting from test execution

A cross-framework assertion issue is about how a test reports a result when one framework’s assertion is used from the other framework. It is not a general explanation for every red or missing result in Xcode.

A build failure means the test code did not reach normal execution. A skipped test has a different status from a test that ran and passed. A test that is missing from the report may belong to a different target, may not have been discovered, or may not have been selected for the run. First confirm the run’s target and status, then use the report to decide whether to inspect assertion behavior. Apple’s testing documentation covers test execution and reading results. (Testing in Xcode)

UI tests and unit tests can have different setup and execution needs. That difference matters when deciding which test target to inspect, but it does not change the first diagnostic question: did this test actually run, and which framework’s assertion produced the result? Keep the investigation focused on that question before changing project settings.

Answers to common interoperability questions

Can one test file contain XCTest and Swift Testing tests?

Swift 6.4 supports gradual use of the two frameworks, but check that the test target and the specific APIs are covered by the documented interoperability behavior. A mixed project does not guarantee that every lifecycle or reporting detail is identical. Confirm discovery and the result for each test type before concluding that sharing a file is the source of the issue.

Why does an XCTest assertion in a Swift Testing test appear not to fail?

Trace the call into the helper that contains the XCTAssert statement. Then inspect the test report for an interoperability diagnostic and check the mode applied to that run. If the test itself was not executed, investigate the target or build first. Replacing the assertion before these checks can hide the actual cause instead of fixing it.

Why can #expect cause trouble in an XCTest test?

#expect is part of the documented Swift Testing interoperability direction, but the presence of that macro alone does not prove that a test ran correctly. Check the Swift toolchain, package tools version, and mode, then see whether the report identifies an interoperability issue. If there is no executed test result, troubleshoot discovery or the build before changing the macro.

What should be checked after upgrading to Swift 6.4?

Record the toolchain used for the run, the package’s swift-tools-version, the affected test target, and the applicable interoperability mode. Compare those facts with the current Apple migration guidance and Swift proposal. A minimal passing test and a minimal failing test can then show whether the report reflects the expected assertion behavior or a separate project problem.

Verify the repair with two small tests

Before calling the issue fixed, create or identify two small tests in the affected target: one with a condition that should pass and one with a condition that should fail. Keep the assertion path relevant to the original symptom, such as the existing helper that wraps an XCTest assertion. The purpose is not to build a new test framework; it is to check whether the report distinguishes the two outcomes.

Run the tests with the same toolchain and package settings used for the course project. Record:

  • the Swift toolchain used for the run;
  • the package’s declared swift-tools-version;
  • the test target and whether each test was discovered;
  • the interoperability mode that applied;
  • the exact warning, failure, skip, or build result.

Compare the run before and after any change. A passing case should remain passing, and the deliberately failing case should produce a visible failure or documented interoperability diagnostic that you can explain. If both tests behave as expected in the minimal setup but the original project still does not, stop changing assertion helpers and check target selection, project configuration, and the full test report.

The official guidance may change as Swift and Xcode evolve. If a newer toolchain produces a result that differs from the documentation checked here, verify the current Swift release notes and migration guidance before carrying a workaround into another course project.

If the code path, mode, and package settings look correct but there is no Mac environment available to run the Xcode project, separate the environment problem from the test problem. Windows alone cannot run Xcode, and relying only on limited school lab access can make repeated checks difficult; buying a Mac may also be more commitment than a single course assignment needs. A remote Mac is a reasonable temporary option when you need a real macOS environment for project tests, but it is a poor fit if your work requires local physical hardware or sustained heavy use. You can compare RUVCLOUD’s Mac access options and available plans before choosing; if you already have a suitable Mac, keep using it and verify the test setup there. The important next step is still the same: rerun the passing and failing cases, then keep the toolchain and test report with the project notes.