Zum Inhalt springen

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

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

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

Framework Use For
Swift Testing New tests, Xcode 16+, parameterized tests
XCTest Legacy projects, UI automation, performance
ViewInspector SwiftUI view testing

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()
// 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 optionals
let user = try #require(await fetchUser())
// Error handling
#expect(throws: MyError.self) { try riskyOperation() }
#expect(throws: Never.self) { try safeOperation() }
XCTAssertEqual(actual, expected)
XCTAssertTrue(condition)
XCTAssertNil(optional)
XCTAssertNotNil(optional)
XCTAssertThrowsError(try expression)
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
}
}
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()
}
}
@Test("Flavor nut content", arguments: zip(
[Flavor.vanilla, .pistachio, .chocolate],
[false, true, false]
))
func testFlavorNuts(flavor: Flavor, expected: Bool) {
#expect(flavor.containsNuts == expected)
}
// 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()
}
}
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 makeSUT
private func makeSUT() -> (UserService, FakeRepository) {
let repo = FakeRepository()
let sut = UserService(repository: repo)
trackForMemoryLeaks(repo)
trackForMemoryLeaks(sut)
return (sut, repo)
}
import ViewInspector
extension MyView: Inspectable {}
@Test("Button displays correct title")
@MainActor
func buttonDisplay() throws {
let sut = MyView()
let button = try sut.inspect().find(button: "Submit")
#expect(try button.labelView().string() == "Submit")
}
Terminal-Fenster
# Check discovery
swift test --list-tests
# Clean build
rm -rf .build && swift build
// Swift Testing: Add time limit
@Test(.timeLimit(.seconds(10)))
func slowTest() async { }
// XCTest: Use expectation
let exp = expectation(description: "done")
wait(for: [exp], timeout: 10.0)
// Check for retain cycles
// Common cause: closures capturing self strongly
class ViewModel {
func load() {
service.fetch { [weak self] result in // weak self!
self?.handle(result)
}
}
}
// Ensure view conforms to Inspectable
extension MyView: Inspectable {}
// Use correct query
try view.inspect().find(button: "Submit") // by label
try view.inspect().find(ViewType.Button.self) // by type
// #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)
  1. makeSUT pattern - Centralize creation + leak tracking
  2. Fakes over mocks - Simpler, more readable
  3. Memory leak tracking - Always in XCTest
  4. One concept per test - Single assertion focus
  5. Async/await - Not completion handlers in tests
  6. Parameterized tests - Use zip() for input/output pairs
Terminal-Fenster
# Xcode
cmd+U # Run all
cmd+click on test # Run specific
# Command line
swift test
swift test --filter MyFeatureTests
xcodebuild test -scheme MyApp -destination 'platform=iOS Simulator,name=iPhone 15'
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

For advanced patterns:

  • reference.md - ViewInspector patterns, Combine testing, advanced async