如何阻止Xcode在fatalError/preconditionFailure时自动断点?
fatalError/preconditionFailure with a Test Host in Xcode I totally get your frustration—when running tests with a Test Host, Xcode’s debugger automatically breaks on these fatal runtime checks, halting your test suite before you can verify the trigger logic. Here’s how to fix this, with two reliable approaches:
Approach 1: Wrap Fatal Calls in Configurable Closures (Best Practice)
The cleanest way is to decouple your framework from direct calls to fatalError/preconditionFailure, making them replaceable for testing.
Step 1: Update Your Framework Code
Create helper structs to wrap these fatal functions:
// In your framework public struct RuntimeErrorHandler { // Wrap fatalError public static var fatalError: (@autoclosure () -> String, StaticString, UInt) -> Never = Swift.fatalError // Wrap preconditionFailure public static var preconditionFailure: (@autoclosure () -> String, StaticString, UInt) -> Never = Swift.preconditionFailure }
Then replace all direct calls to fatalError/preconditionFailure in your framework with the wrapped versions:
// Instead of fatalError("Invalid input") RuntimeErrorHandler.fatalError("Invalid input", #file, #line) // Instead of preconditionFailure("State invalid") RuntimeErrorHandler.preconditionFailure("State invalid", #file, #line)
Step 2: Override the Closures in Tests
In your test class, replace the closures to track triggers instead of crashing:
@testable import YourFramework class FrameworkValidationTests: XCTestCase { private var didTriggerFatal = false private var capturedFatalMessage = "" private var didTriggerPrecondition = false private var capturedPreconditionMessage = "" override func setUp() { super.setUp() // Override fatalError to track instead of crash RuntimeErrorHandler.fatalError = { [weak self] message, _, _ in self?.didTriggerFatal = true self?.capturedFatalMessage = message() XCTFail("Fatal error was triggered: \(message())") Thread.exit() // Exit the test thread without killing the entire suite } // Override preconditionFailure similarly RuntimeErrorHandler.preconditionFailure = { [weak self] message, _, _ in self?.didTriggerPrecondition = true self?.capturedPreconditionMessage = message() XCTFail("Precondition failure was triggered: \(message())") Thread.exit() } } override func tearDown() { // Restore original implementations RuntimeErrorHandler.fatalError = Swift.fatalError RuntimeErrorHandler.preconditionFailure = Swift.preconditionFailure // Reset tracking flags didTriggerFatal = false capturedFatalMessage = "" didTriggerPrecondition = false capturedPreconditionMessage = "" super.tearDown() } // Example test for fatalError trigger func testInvalidInputTriggersFatalError() { // Call the framework method that should trigger fatalError try? frameworkMethodWithInvalidInput() // Verify the trigger XCTAssertTrue(didTriggerFatal) XCTAssertEqual(capturedFatalMessage, "Invalid input") } // Example test for preconditionFailure trigger func testInvalidStateTriggersPreconditionFailure() { frameworkMethodWithInvalidState() XCTAssertTrue(didTriggerPrecondition) XCTAssertEqual(capturedPreconditionMessage, "State invalid") } }
Approach 2: Disable Exceptions Breakpoint for Tests
If you don’t want to modify your framework code, you can adjust Xcode’s breakpoint behavior to skip breaking on fatal errors during tests:
- Open the Breakpoint Navigator (⌘+8).
- Right-click the All Exceptions breakpoint and select Edit Breakpoint.
- In the Condition field, paste this:
!(BOOL)[[NSProcessInfo processInfo].environment objectForKey:@"XCTestConfigurationFilePath"] - Click Done.
This condition tells Xcode to skip the exception breakpoint when running tests (since the XCTestConfigurationFilePath environment variable is only present during test execution). Note this is a global setting, so it will apply to all your test targets.
内容的提问来源于stack exchange,提问作者Gligor

