iOS Testing Expert
Installation
SKILL.md
iOS Testing Expert
Comprehensive testing patterns for iOS apps.
Testing Pyramid for iOS
/\ E2E (XCUITest) - 10%
/ \ UI Tests, full flows
/----\
/ \ Integration - 20%
/ \ ViewModels + Services
/----------\
/ \ Unit - 70%
\ Models, Utils, Pure Logic
XCTest Basics
Unit Test
import XCTest
@testable import App
final class UserTests: XCTestCase {
func testUserFullName() {
let user = User(firstName: "John", lastName: "Doe")
XCTAssertEqual(user.fullName, "John Doe")
}
func testUserValidation() throws {
let user = User(email: "invalid")
XCTAssertThrowsError(try user.validate())
}
}
Async Testing
func testAsyncFetch() async throws {
let service = UserService()
let user = try await service.fetch(id: "123")
XCTAssertEqual(user.id, "123")
}
// Or with expectations
func testAsyncWithExpectation() {
let expectation = expectation(description: "fetch")
service.fetch(id: "123") { result in
XCTAssertNotNil(result)
expectation.fulfill()
}
wait(for: [expectation], timeout: 5)
}
XCUITest (UI Tests)
Basic UI Test
import XCUITest
final class LoginUITests: XCTestCase {
let app = XCUIApplication()
override func setUpWithError() throws {
continueAfterFailure = false
app.launch()
}
func testLogin() throws {
app.textFields["email"].tap()
app.textFields["email"].typeText("test@example.com")
app.secureTextFields["password"].tap()
app.secureTextFields["password"].typeText("password123")
app.buttons["Login"].tap()
XCTAssertTrue(app.staticTexts["Welcome"].waitForExistence(timeout: 5))
}
}
Accessibility Identifiers
// In your SwiftUI View
TextField("Email", text: $email)
.accessibilityIdentifier("email")
// In UI Test
app.textFields["email"].tap()
Quick/Nimble (BDD Style)
Setup
// Package.swift
.package(url: "https://github.com/Quick/Quick.git", from: "7.0.0"),
.package(url: "https://github.com/Quick/Nimble.git", from: "13.0.0"),
BDD Test
import Quick
import Nimble
@testable import App
final class UserSpec: QuickSpec {
override class func spec() {
describe("User") {
var user: User!
beforeEach {
user = User(firstName: "John", lastName: "Doe")
}
context("when valid") {
it("has a full name") {
expect(user.fullName).to(equal("John Doe"))
}
it("can be validated") {
expect { try user.validate() }.toNot(throwError())
}
}
context("when email is invalid") {
beforeEach {
user.email = "invalid"
}
it("throws validation error") {
expect { try user.validate() }.to(throwError())
}
}
}
}
}
Mocking with Protocols
Protocol-Based DI
protocol UserServiceProtocol {
func fetch(id: String) async throws -> User
}
// Real implementation
class UserService: UserServiceProtocol {
func fetch(id: String) async throws -> User {
// Network call
}
}
// Mock for testing
class MockUserService: UserServiceProtocol {
var mockUser: User?
var shouldFail = false
func fetch(id: String) async throws -> User {
if shouldFail { throw APIError.failed }
return mockUser ?? User(id: id)
}
}
Testing with Mock
func testViewModelLoadsUser() async {
let mockService = MockUserService()
mockService.mockUser = User(id: "1", name: "Test")
let viewModel = UserViewModel(service: mockService)
await viewModel.load()
XCTAssertEqual(viewModel.user?.name, "Test")
}
Snapshot Testing
swift-snapshot-testing
import SnapshotTesting
import SwiftUI
func testUserProfileView() {
let view = UserProfileView(user: .mock)
assertSnapshot(
of: view,
as: .image(layout: .device(config: .iPhone13))
)
}
Test Coverage
Enable Coverage
xcodebuild test \
-scheme App \
-enableCodeCoverage YES \
-resultBundlePath TestResults.xcresult
View Report
xcrun xccov view --report TestResults.xcresult
Use when: Writing iOS tests, setting up testing infrastructure, mocking
Related skills
More from willsigmon/sigstack
fastlane expert
Fastlane automation - build, test, deploy iOS apps, TestFlight, App Store Connect
39swiftlint-autofix
Run SwiftFormat and fix all auto-correctable SwiftLint issues
30manus ai agent integration
Use this skill when delegating complex autonomous tasks to Manus AI - an AI agent that can browse the web, execute code, generate files, and comple...
24find-bug-root-cause
Deep investigation to find actual root cause (not just symptoms)
23dead code eliminator
Find and delete unused files, disabled code blocks, Enhanced variants, deprecated features, test files in production for Leavn app
22bitrise expert
Bitrise CI/CD - iOS builds, automated testing, deployment, managed macOS runners
21