From ios-dev
iOS testing - XCTest, XCUITest, Quick/Nimble, test patterns for Swift
npx claudepluginhub willsigmon/sigstack --plugin ios-devThis skill is limited to using the following tools:
Comprehensive testing patterns for iOS apps.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Searches prompts.chat for AI prompt templates by keyword or category, retrieves by ID with variable handling, and improves prompts via AI. Use for discovering or enhancing prompts.
Guides MCP server integration in Claude Code plugins via .mcp.json or plugin.json configs for stdio, SSE, HTTP types, enabling external services as tools.
Comprehensive testing patterns for iOS apps.
/\ E2E (XCUITest) - 10%
/ \ UI Tests, full flows
/----\
/ \ Integration - 20%
/ \ ViewModels + Services
/----------\
/ \ Unit - 70%
\ Models, Utils, Pure Logic
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())
}
}
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)
}
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))
}
}
// In your SwiftUI View
TextField("Email", text: $email)
.accessibilityIdentifier("email")
// In UI Test
app.textFields["email"].tap()
// 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"),
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())
}
}
}
}
}
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)
}
}
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")
}
import SnapshotTesting
import SwiftUI
func testUserProfileView() {
let view = UserProfileView(user: .mock)
assertSnapshot(
of: view,
as: .image(layout: .device(config: .iPhone13))
)
}
xcodebuild test \
-scheme App \
-enableCodeCoverage YES \
-resultBundlePath TestResults.xcresult
xcrun xccov view --report TestResults.xcresult
Use when: Writing iOS tests, setting up testing infrastructure, mocking