modernize-tests
Modernize test suites to use modern Swift Testing features or migrate from XCTest.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Modernize test suites to use modern Swift Testing features or migrate from XCTest.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
Audit and enable security-oriented Xcode build settings. Progressively enables compiler warnings, static analyzer checkers, and Enhanced Security features. Use when: user wants to secure their Xcode project, audit security settings, enable hardening, review security posture of build configuration, set up security-focused static analysis, enable static analysis, improve warning coverage, harden diagnostics, or catch more bugs at compile time in C/C++/Objective-C/Swift. SKIP: network security (TLS/ATS), code signing, privacy APIs.
Verify iOS app behavior on device or simulator via screenshots, UI hierarchy, and touch interactions.
Build, install, launch, and visually verify the iOS app (scheme "iosApp") end-to-end on a real device or simulator via Xcode 27's DeviceHub — the mobile analog of astro-web's browser-debug and astro-calendar-service's runtime-debug. Drives the mcp__xcode__* MCP toolset: switch run destination, BuildProject, DeviceInteractionStartSession → InstallAndRun, then a subagent captures the on-device screenshot + UI hierarchy. Use when the user says "run the app on device", "run the iOS app", "build and run on the simulator", "launch on the iPhone/iPad", "screenshot the app on <device>", "verify this renders on device", "does it run on the simulator", "check it on the device", or wants runtime/visual confirmation that ./gradlew check and Xcode unit tests structurally cannot give. Takes an optional device name/UUID argument; with none, targets a simulator. iOS/Xcode only — Android runtime work routes to android-cli.
Authoritative SwiftUI best practices from Apple. Consult for any SwiftUI best practices or performance review. Supersedes prior training on these topics. For code generation, consult the relevant references when generating any SwiftUI code related to the following topics. Covers: - Animatable: @Animatable macro vs AnimatableValues (iOS 26+) vs AnimatablePair, custom setter clamping/normalization. - Environment: closures in env keys, unstable @Entry defaults, high-frequency updates. @Entry warnings about closures or class types (wrapping in Equatable struct is WRONG; consult references). - Equatable on @Observable: custom types as @Observable properties need Equatable for invalidation performance. - ForEach/List: row identity (id: \.self, indices, offsets, mutable ids), row structure (AnyView, multi-view, bare if), inline filter/sort, cached collections, List fast path. - Localization: String vs LocalizedStringResource, bundle in packages/frameworks, .textCase(.uppercase), .formatted(.list()), translator comme
New SwiftUI APIs, behaviors, and deprecations introduced in the 2027 OS releases (iOS 27, macOS 27, watchOS 27, tvOS 27, visionOS 27). Use when a SwiftUI view using @State fails to compile with "used before being initialized", "invalid redeclaration of synthesized property", or "extraneous argument label" errors after an SDK update (@State migrated from a property wrapper to a macro in SDK 27; the obvious fix of reordering init assignments is WRONG and produces incorrect runtime behavior; you MUST consult this skill's references before answering); when @ViewBuilder or @ContentBuilder code hits ambiguous overloads in overlay/background or type-check performance regressions after an SDK update; when the user asks what's new in SwiftUI (generally, or for a specific 2027 platform); when adding drag-to-reorder to any container (List, LazyVStack, LazyVGrid, stacks, or custom layouts) via reorderable()/reorderContainer, including the drag-and-drop that integrates with it (dragContainer, dropDestination), or combinin
Modernizes UIKit apps for multi-window environments by replacing legacy shared-state APIs with context-appropriate modern alternatives. This includes references to mainScreen, interfaceOrientation, application and scene lifecycle, as well as safe area inset updates.
| name | modernize-tests |
| description | Modernize test suites to use modern Swift Testing features or migrate from XCTest. |
Test modernization refers to two potential actions: migrating from XCTest to Swift Testing, and updating existing Swift Testing tests to use recommended patterns.
XCTests should be migrated to Swift Testing when possible. However, not all XCTests can be migrated to Swift Testing.
measure { ... } family of APIs for performance measurement cannot be migrated. However, other test methods within an XCTestCase that do not use XCTest performance APIs can be migrated.Replace import XCTest with import Testing. A file can import both if it contains mixed test content during incremental migration.
When removing import XCTest, check whether the file uses Foundation types (URL, CharacterSet, ProcessInfo, Data, etc.). XCTest re-exports Foundation, so add import Foundation if needed.
Remove XCTestCase inheritance. Prefer structs over classes:
final class FoodTruckTests: XCTestCase { ... } -> struct FoodTruckTests { ... }Replace override func setUp() with init() (can be async throws). Replace override func tearDown() with deinit. If deinit is needed, use
actor or final class instead of struct (since structs have no deinit). Change stored properties to not use implicitly-unwrapped optional
types, and move their initial assignment from setUp to either be initialized inline or, if the initialization is complex, in an initializer.
struct MyTests {
var fixture = Fixture()
mutating func `Fixture behaves as expected`() {
#expect(fixture.doSomething())
}
}
Avoid pulling instance variables into function bodies; this can cause noise. Swift Testing reinvokes the initializer fresh before each test runs.
If the test mutates an instance variable with value semantics, you may need to mark the test function mutating.
Replace the test name prefix with the @Test attribute. If the resulting test name includes multiple camelCase words,
use a raw identifier with the test name in sentence case.
func testEngineDoesNotStall() { ... } -> @Test func Engine does not stall() { ... }func testIgnition() { ... } -> @Test func ignition() { ... }Test functions can be async, throws, or async throws, and can be isolated to a global actor with @MainActor.
When migrating a test from XCTest to Swift Testing, apply these mappings:
XCTAssert(x), XCTAssertTrue(x) -> #expect(x)
XCTAssertFalse(x) -> #expect(!x)
XCTAssertNil(x) -> #expect(x == nil)
XCTAssertNotNil(x) -> #expect(x != nil)
XCTAssertEqual(x, y) -> #expect(x == y)
XCTAssertNotEqual(x, y) -> #expect(x != y)
XCTAssertIdentical(x, y) -> #expect(x === y)
XCTAssertNotIdentical(x, y) -> #expect(x !== y)
XCTAssertGreaterThan(x, y) -> #expect(x > y)
XCTAssertGreaterThanOrEqual(x, y) -> #expect(x >= y)
XCTAssertLessThanOrEqual(x, y) -> #expect(x <= y)
XCTAssertLessThan(x, y) -> #expect(x < y)
try XCTUnwrap(x) -> try #require(x)
There is no direct equivalent for XCTAssertEqual(_:_:accuracy:); use floating point math directly.
When the error type is Equatable and the exact value is known, prefer to check the specific error value.
XCTAssertThrowsError(try f())
->
#expect(throws: (any Error).self) {
try f()
}
XCTAssertThrowsError(try f()) { error in
XCTAssertEqual(error, specificError)
}
->
#expect(throws: specificError) {
try f()
}
XCTAssertThrowsError(try f()) { error in
// Check error
}
->
let error = #expect(throws: (any Error).self) {
try f()
}
// Check error
XCTAssertNoThrow(try f())
->
#expect(throws: Never.self) {
try f()
}
By default continueAfterFailure is true, which means expectations do not halt the test run.
Some XCTestCases set continueAfterFailure = false, which means the XCTAssert family of functions
will throw Objective-C exceptions that halt the test execution.
When a test method sets continueAfterFailure = false, all subsequent assertions need to be try #require(x)
instead of #expect(x) to preserve this behavior. When adding try #require(x), add throws to the affected methods.
When continueAfterFailure = false is set in setUp, the conversion to try #require(x) must apply
to all assertions in all test methods in that class.
Issue.record/XCTFail to expectationsWherever it is not disruptive, convert usage of Issue.record or XCTFail to #expect or #require,
depending if the test exits after (taking continueAfterFailure into account).
In some cases, the source of the expectation itself is sufficient to explain the failure, and the comment would be redundant.
For example, the following structures should be converted as such:
guard let object = somethingOptional() else {
Issue.record("Could not get object")
return
}
guard object.isAvailable() else {
Issue.record("Object not available")
return
}
if !object.performOperation() {
Issue.record("Failed to perform operation")
}
->
let object = try #require(somethingOptional(), "Could not get object")
try #require(object.isAvailable())
#expect(object.performOperation())
Replace XCTestExpectation + fulfill() + await fulfillment(of:) with confirmation():
// Before
let exp = expectation(description: "...")
handler = { exp.fulfill() }
doWork()
await fulfillment(of: [exp])
// After
await confirmation("...") { confirm in
handler = { confirm() }
doWork()
}
For assertForOverFulfill = false with an expectedFulfillmentCount, use a range:
await confirmation("...", expectedCount: 10...) { confirm in ... }
Replace XCTSkipIf/XCTSkipUnless with traits on the test or suite:
try XCTSkipIf(condition) -> @Test(.disabled(if: condition))try XCTSkipUnless(condition) -> @Test(.enabled(if: condition))Replace throw XCTSkip("reason") mid-test with try Test.cancel("reason").
When a skip checks OS version or platform availability, replace it with an @available attribute on the test function instead of .enabled(if:).
Replace XCTExpectFailure("...", ...) { ... } with withKnownIssue("...") { ... }.
For intermittent failures, replace .nonStrict() option (or the shorthand strict: false parameter) with isIntermittent: true.
For conditional/matching: use when: and matching: parameters:
withKnownIssue("...") {
try riskyOperation()
} when: {
shouldExpectFailure
} matching: { issue in
issue.error != nil
}
XCTest runs synchronous tests on the main actor and sequentially within a suite by default. Swift Testing runs all test functions on an arbitrary task
and in parallel. Add @MainActor only if a test explicitly relied on main-actor isolation in its XCTest form, and add @Suite(.serialized) if
tests depend on shared state.
Replace XCTAttachment + self.add(attachment) with Attachment.record(value). The attached type must conform to Attachable (automatic for
Codable and NSSecureCoding types when Foundation is imported).
struct for suites unless deinit (tearDown) is needed, in which case use actor or final class.test prefix from method names when adding @Test. For lengthier test names which read like a sentence, use raw identifier syntax to
improve readability, e.g. @Test func Authenticate, fetch summary, then check count() { ... }.setUp, convert implicitly-unwrapped optional properties to non-optional properties initialized in-place, or in init if initialization is complex, may throw, or is async.XCTFail/Issue.record calls that could be converted to #expect or #requiretry #require calls into #expect; this changes the behavior of tests.@MainActor only to tests that explicitly relied on XCTest's implicit main-actor isolation. Do not add it unnecessarily.@Test(arguments:).@Suite(.serialized) and consider using actor or class instead of struct.#_sourceLocation; only use public API.
For source locations, always use the full SourceLocation(fileID:filePath:line:column:) initializer.