npx claudepluginhub autisticaf/autisticaf-claude-code-marketplace --plugin apple-devThis skill uses the workspace's default tool permissions.
> **First step:** Tell the user: "swift-testing skill loaded."
Generates design tokens/docs from CSS/Tailwind/styled-components codebases, audits visual consistency across 10 dimensions, detects AI slop in UI.
Records polished WebM UI demo videos of web apps using Playwright with cursor overlay, natural pacing, and three-phase scripting. Activates for demo, walkthrough, screen recording, or tutorial requests.
Delivers idiomatic Kotlin patterns for null safety, immutability, sealed classes, coroutines, Flows, extensions, DSL builders, and Gradle DSL. Use when writing, reviewing, refactoring, or designing Kotlin code.
First step: Tell the user: "swift-testing skill loaded."
The Swift Testing framework (import Testing) is Apple's modern test framework introduced in Xcode 16. It replaces XCTest's XCTAssert family with expressive macros (#expect, #require), supports parameterized tests, traits for conditional execution, and organizes tests with @Suite and @Test attributes.
@Test, #expect, #require, @Suite, traits)Do NOT activate for these — use the specialized skill instead:
generators-test-generatortesting-tdd-feature or testing-tdd-bug-fix| Scenario | Use |
|---|---|
| New test target (Xcode 16+) | Swift Testing |
| UI tests (XCUIApplication) | XCTest (Swift Testing does not support UI testing) |
| Performance tests (measure blocks) | XCTest (Swift Testing has no performance API) |
| Objective-C test code | XCTest (Swift Testing is Swift-only) |
| Parameterized / data-driven tests | Swift Testing (first-class support) |
| Existing XCTest target, adding new tests | Either; they coexist in the same target |
| KIF or EarlGrey UI tests | XCTest |
| Requirement | Minimum |
|---|---|
| Import | import Testing |
| Xcode | 16.0+ |
| Swift | 6.0+ |
| iOS deployment target | 16.0+ |
| macOS deployment target | 13.0+ |
| watchOS deployment target | 9.0+ |
| tvOS deployment target | 16.0+ |
| visionOS deployment target | 1.0+ |
Swift Testing ships with the Xcode toolchain, not the OS SDK. Apps targeting iOS 16+ can use it as long as they build with Xcode 16+.
import Testing
@Test("Addition produces correct sum")
func addition() {
let result = 2 + 3
#expect(result == 5)
}
#expect captures the full expression on failure, showing both sides of the comparison automatically. No need for separate assertEqual / assertTrue variants.
#require throws on failure, stopping the test immediately. Use it for preconditions and optional unwrapping.
@Test func loadUser() throws {
let data = try #require(JSONLoader.load("user.json"))
let user = try #require(User(data: data))
#expect(user.name == "Alice")
}
@Suite("Authentication Tests")
struct AuthenticationTests {
let service: AuthService
init() {
// Runs before each @Test (equivalent to setUp)
service = AuthService(store: MockTokenStore())
}
@Test func loginSucceeds() async throws {
let token = try await service.login(user: "admin", password: "secret")
#expect(token.isValid)
}
@Test func loginFailsWithBadPassword() async throws {
await #expect(throws: AuthError.invalidCredentials) {
try await service.login(user: "admin", password: "wrong")
}
}
}
init() replaces XCTest's setUp() -- runs before each test.deinit replaces tearDown() -- runs after each test (requires the suite to be a class).Traits modify test behavior. Pass them as arguments to @Test or @Suite.
@Test(.enabled(if: ProcessInfo.processInfo.environment["CI"] != nil))
func onlyOnCI() {
// Runs only in CI
}
@Test(.disabled("Blocked by #1234"))
func brokenFeature() {
// Skipped with a reason
}
@Test(.bug("https://github.com/org/repo/issues/42", "Crash on empty input"))
func emptyInputHandling() {
#expect(Parser.parse("") == .empty)
}
extension Tag {
@Tag static var networking: Self
@Tag static var database: Self
}
@Test(.tags(.networking))
func fetchData() async throws {
let data = try await APIClient.shared.fetch("/items")
#expect(!data.isEmpty)
}
Run only tagged tests from the command line:
swift test --filter .tags:networking
@Test(.timeLimit(.minutes(1)))
func longRunningOperation() async throws {
let result = try await processor.run(largeDataset)
#expect(result.isComplete)
}
@Test(
"Sync engine completes within time limit",
.tags(.networking, .database),
.timeLimit(.minutes(2)),
.enabled(if: FeatureFlags.syncV2Enabled)
)
func syncEngine() async throws {
// ...
}
@Test(arguments: ["hello", "world", "swift"])
func stringIsNotEmpty(_ value: String) {
#expect(!value.isEmpty)
}
@Test(arguments: [1, 2, 3], ["a", "b", "c"])
func pairCombinations(number: Int, letter: String) {
#expect(!letter.isEmpty)
#expect(number > 0)
}
This produces the Cartesian product: (1,"a"), (1,"b"), (1,"c"), (2,"a"), ... (9 total).
@Test(arguments: zip([1, 2, 3], ["one", "two", "three"]))
func zippedPairs(number: Int, name: String) {
#expect(!name.isEmpty)
}
enum Direction: CaseIterable {
case north, south, east, west
}
@Test(arguments: Direction.allCases)
func directionHasOpposite(_ direction: Direction) {
#expect(direction.opposite.opposite == direction)
}
When test arguments are complex types, conform to CustomTestStringConvertible to get readable test names in Xcode's test navigator.
struct UserFixture: CustomTestStringConvertible, Sendable {
let name: String
let age: Int
let expectedTier: MembershipTier
var testDescription: String { "\(name) (age \(age))" }
}
@Test(arguments: [
UserFixture(name: "Teen", age: 15, expectedTier: .junior),
UserFixture(name: "Adult", age: 30, expectedTier: .standard),
UserFixture(name: "Senior", age: 65, expectedTier: .senior),
])
func membershipTier(_ fixture: UserFixture) {
let tier = MembershipTier.for(age: fixture.age)
#expect(tier == fixture.expectedTier)
}
@Test func invalidInputThrows() {
#expect(throws: ValidationError.self) {
try Validator.validate("")
}
}
@Test func invalidInputThrowsSpecificError() {
#expect(throws: ValidationError.emptyField("name")) {
try Validator.validate(field: "name", value: "")
}
}
@Test func errorContainsContext() throws {
let error = try #require(throws: NetworkError.self) {
try client.fetchSync(url: badURL)
}
#expect(error.statusCode == 404)
#expect(error.url == badURL)
}
// #expect does NOT throw -- this naturally fails if an error is thrown:
@Test func validInputSucceeds() throws {
let result = try Validator.validate("hello")
#expect(result.isValid)
}
Async tests work naturally. Mark the test function async and/or throws.
@Test func fetchUserProfile() async throws {
let profile = try await api.fetchProfile(id: "user-1")
#expect(profile.name == "Alice")
}
@Test func notificationPosted() async {
await confirmation("UserDidLogin notification received") { confirm in
let observer = NotificationCenter.default.addObserver(
forName: .userDidLogin, object: nil, queue: .main
) { _ in
confirm()
}
LoginManager.shared.login(user: "test", password: "test")
NotificationCenter.default.removeObserver(observer)
}
}
For multiple expected confirmations:
await confirmation("Progress callbacks", expectedCount: 3) { confirm in
downloader.onProgress = { _ in confirm() }
await downloader.download(url: fileURL)
}
| XCTest | Swift Testing |
|---|---|
XCTAssertTrue(x) | #expect(x) |
XCTAssertFalse(x) | #expect(!x) |
XCTAssertEqual(a, b) | #expect(a == b) |
XCTAssertNotEqual(a, b) | #expect(a != b) |
XCTAssertNil(x) | #expect(x == nil) |
XCTAssertNotNil(x) | #expect(x != nil) or try #require(x) |
XCTAssertGreaterThan(a, b) | #expect(a > b) |
XCTAssertThrowsError(expr) | #expect(throws: SomeError.self) { expr } |
XCTUnwrap(x) | try #require(x) |
XCTFail("msg") | Issue.record("msg") |
| XCTest | Swift Testing |
|---|---|
class FooTests: XCTestCase | @Suite struct FooTests |
func testBar() | @Test func bar() |
override func setUp() | init() |
override func tearDown() | deinit (class suites only) |
XCTestExpectation + wait(for:) | confirmation { } |
addTeardownBlock { } | deinit or explicit cleanup in test |
// ❌ XCTest style
import XCTest
class ParserTests: XCTestCase {
var parser: Parser!
override func setUp() {
super.setUp()
parser = Parser()
}
func testParseValidJSON() throws {
let result = try parser.parse("{\"key\": \"value\"}")
XCTAssertNotNil(result)
XCTAssertEqual(result?["key"] as? String, "value")
}
func testParseInvalidJSONThrows() {
XCTAssertThrowsError(try parser.parse("not json"))
}
}
// ✅ Swift Testing style
import Testing
@Suite struct ParserTests {
let parser = Parser()
@Test func parseValidJSON() throws {
let result = try #require(parser.parse("{\"key\": \"value\"}"))
#expect(result["key"] as? String == "value")
}
@Test func parseInvalidJSONThrows() {
#expect(throws: ParserError.self) {
try parser.parse("not json")
}
}
}
@Test or @Suite in the gutter# Run all tests
swift test
# Filter by test name
swift test --filter "ParserTests"
# Filter by tag
swift test --filter .tags:networking
Swift Testing and XCTest coexist in the same test target. Rules:
XCTestCase in Swift Testing suites.@Test on methods inside an XCTestCase subclass.// File: ParserXCTests.swift -- XCTest
import XCTest
class ParserXCTests: XCTestCase {
func testLegacyBehavior() {
XCTAssertEqual(Parser.legacyMode, true)
}
}
// File: ParserSwiftTests.swift -- Swift Testing
import Testing
@Suite struct ParserSwiftTests {
@Test func modernBehavior() {
#expect(Parser.modernMode == true)
}
}
// ❌ Bad: loses context on failure
#expect(result == true)
#expect(array.count == 3)
// ✅ Good: use the natural expression
#expect(result)
#expect(array.count == 3) // this is fine -- #expect shows both sides
// ❌ Bad: force unwrap crashes the test runner
let user = users.first!
#expect(user.name == "Alice")
// ✅ Good: #require fails gracefully and stops the test
let user = try #require(users.first)
#expect(user.name == "Alice")
// ❌ Bad: unnecessary class when no tearDown is needed
@Suite class CalculatorTests {
@Test func add() { #expect(Calculator.add(2, 3) == 5) }
}
// ✅ Good: struct with init for setup
@Suite struct CalculatorTests {
let calculator = Calculator()
@Test func add() { #expect(calculator.add(2, 3) == 5) }
}
// ❌ Bad: duplicated tests
@Test func parseCSVComma() throws {
let result = try CSVParser.parse("a,b,c")
#expect(result == ["a", "b", "c"])
}
@Test func parseCSVSemicolon() throws {
let result = try CSVParser.parse("a;b;c", delimiter: ";")
#expect(result == ["a", "b", "c"])
}
// ✅ Good: parameterized
struct CSVCase: CustomTestStringConvertible, Sendable {
let input: String
let delimiter: Character
let expected: [String]
var testDescription: String { "delimiter: \(delimiter)" }
}
@Test(arguments: [
CSVCase(input: "a,b,c", delimiter: ",", expected: ["a", "b", "c"]),
CSVCase(input: "a;b;c", delimiter: ";", expected: ["a", "b", "c"]),
])
func parseCSV(_ testCase: CSVCase) throws {
let result = try CSVParser.parse(testCase.input, delimiter: testCase.delimiter)
#expect(result == testCase.expected)
}
// ❌ Bad: runtime skip with guard
@Test func networkTest() async throws {
guard ProcessInfo.processInfo.environment["CI"] != nil else { return }
// ...
}
// ✅ Good: trait makes intent clear and shows "skipped" in results
@Test(.enabled(if: ProcessInfo.processInfo.environment["CI"] != nil))
func networkTest() async throws {
// ...
}