testing-swift
| Type | Skill |
| Plugin | awl-testing · v0.0.27 |
| Invoke | /awl-testing:testing-swift |
| Tools | Read, Write, Edit, Bash, Glob, Grep |
| Source | plugins/awl-testing/skills/testing-swift/SKILL.md |
When Claude uses it
Section titled “When Claude uses it”Writes iOS tests using Swift Testing framework and XCTest. Tests SwiftUI views with ViewInspector. Use when “write swift tests”, “xctest”, “swift testing”, “viewinspector”, “test ios”, “test swiftui”, “test viewmodel swift”, “#expect”, “#require”, “create fake swift”, “fix swift test”, “debug ios test”, “parameterized test swift”.
Trigger phrases: write swift tests · xctest · swift testing · viewinspector · test ios · test swiftui · test viewmodel swift · #expect · #require · create fake swift · fix swift test · debug ios test · parameterized test swift
Definition
Section titled “Definition”Quick Start
Section titled “Quick Start”Swift Testing (Modern)
import Testing
@Suite("UserService")struct UserServiceTests { @Test("Fetches user successfully") func fetchUser() async throws { let sut = UserService(repository: FakeUserRepository()) let user = try await sut.fetchUser(id: "123") #expect(user.name == "Alice") }}XCTest (Legacy)
final class UserServiceTests: XCTestCase { func test_fetchUser_returnsUser() async throws { let (sut, _) = makeSUT() let user = try await sut.fetchUser(id: "123") XCTAssertEqual(user.name, "Alice") }
private func makeSUT() -> (UserService, FakeUserRepository) { let repo = FakeUserRepository() let sut = UserService(repository: repo) trackForMemoryLeaks(sut) return (sut, repo) }}Run tests: cmd+U or swift test
When to Use
Section titled “When to Use”| Framework | Use For |
|---|---|
| Swift Testing | New tests, Xcode 16+, parameterized tests |
| XCTest | Legacy projects, UI automation, performance |
| ViewInspector | SwiftUI view testing |
Naming Conventions
Section titled “Naming Conventions”Swift Testing: Descriptive @Test("Description")
@Test("User login succeeds with valid credentials")@Test("Shopping cart calculates total correctly")XCTest: test_feature_whenCondition_thenOutcome
func test_login_whenValidCredentials_thenSucceeds()func test_cart_whenItemAdded_thenTotalUpdates()Swift Testing Assertions
Section titled “Swift Testing Assertions”// Two macros for everything#expect(value == expected) // Soft check, continues#require(value != nil) // Hard check, stops
// Common patterns#expect(user.name == "Alice")#expect(list.isEmpty)#expect(count > 0)
// Unwrap optionalslet user = try #require(await fetchUser())
// Error handling#expect(throws: MyError.self) { try riskyOperation() }#expect(throws: Never.self) { try safeOperation() }XCTest Assertions
Section titled “XCTest Assertions”XCTAssertEqual(actual, expected)XCTAssertTrue(condition)XCTAssertNil(optional)XCTAssertNotNil(optional)XCTAssertThrowsError(try expression)Test Doubles
Section titled “Test Doubles”Fakes (Preferred)
Section titled “Fakes (Preferred)”class FakeUserRepository: UserRepository { var users: [String: User] = [:] var shouldFail = false
func fetchUser(id: String) async throws -> User { if shouldFail { throw RepositoryError.notFound } guard let user = users[id] else { throw RepositoryError.notFound } return user }}Spy (Record Calls)
Section titled “Spy (Record Calls)”class NetworkServiceSpy: NetworkService { var fetchCalls: [(endpoint: String, params: [String: Any])] = []
func fetch(endpoint: String, params: [String: Any]) async throws -> Data { fetchCalls.append((endpoint, params)) return Data() }}Parameterized Tests (Swift Testing)
Section titled “Parameterized Tests (Swift Testing)”@Test("Flavor nut content", arguments: zip( [Flavor.vanilla, .pistachio, .chocolate], [false, true, false]))func testFlavorNuts(flavor: Flavor, expected: Bool) { #expect(flavor.containsNuts == expected)}Async Testing
Section titled “Async Testing”// Swift Testing@Test("Async operation")func asyncOperation() async throws { let result = try await performAsyncWork() #expect(result.isValid)}
// Confirmations (replaces XCTestExpectation)@Test("Delegate called")func delegateNotifications() async { await confirmation("didUpdate called") { confirm in let delegate = MockDelegate { confirm() } sut.performAction() }}Memory Leak Tracking (XCTest)
Section titled “Memory Leak Tracking (XCTest)”extension XCTestCase { func trackForMemoryLeaks(_ instance: AnyObject, file: StaticString = #filePath, line: UInt = #line) { addTeardownBlock { [weak instance] in XCTAssertNil(instance, "Memory leak", file: file, line: line) } }}
// Usage in makeSUTprivate func makeSUT() -> (UserService, FakeRepository) { let repo = FakeRepository() let sut = UserService(repository: repo) trackForMemoryLeaks(repo) trackForMemoryLeaks(sut) return (sut, repo)}SwiftUI Testing (ViewInspector)
Section titled “SwiftUI Testing (ViewInspector)”import ViewInspector
extension MyView: Inspectable {}
@Test("Button displays correct title")@MainActorfunc buttonDisplay() throws { let sut = MyView() let button = try sut.inspect().find(button: "Submit") #expect(try button.labelView().string() == "Submit")}Troubleshooting
Section titled “Troubleshooting”Test Not Running
Section titled “Test Not Running”# Check discoveryswift test --list-tests
# Clean buildrm -rf .build && swift buildAsync Test Timeout
Section titled “Async Test Timeout”// Swift Testing: Add time limit@Test(.timeLimit(.seconds(10)))func slowTest() async { }
// XCTest: Use expectationlet exp = expectation(description: "done")wait(for: [exp], timeout: 10.0)Memory Leak Detected
Section titled “Memory Leak Detected”// Check for retain cycles// Common cause: closures capturing self stronglyclass ViewModel { func load() { service.fetch { [weak self] result in // weak self! self?.handle(result) } }}ViewInspector Can’t Find Element
Section titled “ViewInspector Can’t Find Element”// Ensure view conforms to Inspectableextension MyView: Inspectable {}
// Use correct querytry view.inspect().find(button: "Submit") // by labeltry view.inspect().find(ViewType.Button.self) // by type#expect vs #require
Section titled “#expect vs #require”// #require stops test on failure (good for preconditions)let user = try #require(await fetchUser()) // Test stops if nil
// #expect continues (good for multiple checks)#expect(user.name == "Alice") // Test continues if fails#expect(user.age > 0)Best Practices
Section titled “Best Practices”- makeSUT pattern - Centralize creation + leak tracking
- Fakes over mocks - Simpler, more readable
- Memory leak tracking - Always in XCTest
- One concept per test - Single assertion focus
- Async/await - Not completion handlers in tests
- Parameterized tests - Use
zip()for input/output pairs
Running Tests
Section titled “Running Tests”# Xcodecmd+U # Run allcmd+click on test # Run specific
# Command lineswift testswift test --filter MyFeatureTestsxcodebuild test -scheme MyApp -destination 'platform=iOS Simulator,name=iPhone 15'Migration: XCTest to Swift Testing
Section titled “Migration: XCTest to Swift Testing”| XCTest | Swift Testing |
|---|---|
class: XCTestCase |
struct |
func testX() |
@Test func x() |
XCTAssertEqual(a, b) |
#expect(a == b) |
XCTAssertNil(x) |
#expect(x == nil) |
try XCTUnwrap(x) |
try #require(x) |
setUp()/tearDown() |
init()/deinit |
References
Section titled “References”For advanced patterns:
- reference.md - ViewInspector patterns, Combine testing, advanced async

