Test SwiftUI apps with XCTest, UI tests, snapshot testing, and async testing. Use when writing unit tests for ViewModels, creating UI automation tests, implementing snapshot tests, or testing async code.
View on GitHubfusengine/agents
fuse-swift-apple-expert
plugins/swift-apple-expert/skills/swiftui-testing/SKILL.md
January 22, 2026
Select agents to install to:
npx add-skill https://github.com/fusengine/agents/blob/main/plugins/swift-apple-expert/skills/swiftui-testing/SKILL.md -a claude-code --skill swiftui-testingInstallation paths:
.claude/skills/swiftui-testing/# SwiftUI Testing
## Unit Testing ViewModels
```swift
@MainActor
final class ProfileViewModelTests: XCTestCase {
var sut: ProfileViewModel!
var mockRepository: MockUserRepository!
override func setUp() {
super.setUp()
mockRepository = MockUserRepository()
sut = ProfileViewModel(repository: mockRepository)
}
override func tearDown() {
sut = nil
mockRepository = nil
super.tearDown()
}
func testLoadUser_Success() async {
// Given
let expectedUser = User(id: "1", name: "John")
mockRepository.stubbedUser = expectedUser
// When
await sut.loadUser(id: "1")
// Then
XCTAssertEqual(sut.user, expectedUser)
XCTAssertFalse(sut.isLoading)
XCTAssertNil(sut.error)
}
func testLoadUser_Failure() async {
// Given
mockRepository.shouldFail = true
// When
await sut.loadUser(id: "1")
// Then
XCTAssertNil(sut.user)
XCTAssertNotNil(sut.error)
}
}
```
## Mock Repository Pattern
```swift
final class MockUserRepository: UserRepositoryProtocol, @unchecked Sendable {
var stubbedUser: User?
var shouldFail = false
var fetchCallCount = 0
func fetch(id: String) async throws -> User {
fetchCallCount += 1
if shouldFail {
throw NSError(domain: "Test", code: -1)
}
return stubbedUser ?? User(id: id, name: "Test")
}
}
```
## UI Testing with Accessibility
```swift
final class ProfileUITests: XCTestCase {
var app: XCUIApplication!
override func setUp() {
continueAfterFailure = false
app = XCUIApplication()
app.launchArguments = ["UI_TESTING"]
app.launch()
}
func testProfileFlow() {
// Navigate to profile
let profileTab = app.tabBars.buttons["Profile"]
XCTAssertTrue(profileTab.waitForExistence(timeout: 5))
profileTab.tap()
// Ve