Ios testing
Skill ahmed3elshaer/everything-claude-code-mobile/skills/ios-testing
iOS testing with XCTest. Unit tests, UI tests, mocks, and test patterns for Swift/SwiftUI.From its SKILL.md
npx -y skills add ahmed3elshaer/everything-claude-code-mobile --skill ios-testingAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
SKILL.md
6.8 KB, as published. Nobody here has run it
iOS Testing
Testing patterns for Swift and SwiftUI apps.
Unit Tests
Basic Unit Test
import XCTest
@testable import MyApp
class UserViewModelTests: XCTestCase {
var sut: UserViewModel!
var mockService: MockUserService!
override func setUp() {
super.setUp()
mockService = MockUserService()
sut = UserViewModel(service: mockService)
}
override func tearDown() {
sut = nil
mockService = nil
super.tearDown()
}
func testLoadUsers_whenSuccessful_populatesUsers() async throws {
// Given
let expectedUsers = [User.mock1, User.mock2]
mockService.usersToReturn = expectedUsers
// When
await sut.loadUsers()
// Then
XCTAssertEqual(sut.users, expectedUsers)
XCTAssertFalse(sut.isLoading)
XCTAssertNil(sut.errorMessage)
}
func testLoadUsers_whenFailure_setsErrorMessage() async throws {
// Given
mockService.shouldThrowError = true
// When
await sut.loadUsers()
// Then
XCTAssertTrue(sut.users.isEmpty)
XCTAssertNotNil(sut.errorMessage)
}
}
Mocking
// ✅ Protocol-based mocking
protocol UserServiceProtocol {
func getUsers() async throws -> [User]
}
class MockUserService: UserServiceProtocol {
var usersToReturn: [User] = []
var shouldThrowError = false
var getUsersCalled = false
func getUsers() async throws -> [User] {
getUsersCalled = true
if shouldThrowError {
throw NetworkError.noConnection
}
return usersToReturn
}
}
// ✅ Verify method calls
func testRefreshButton_callsService() async {
// When
await sut.refresh()
// Then
XCTAssertTrue(mockService.getUsersCalled)
}
Async Testing
func testAsyncOperation_completesSuccessfully() async throws {
// Given
let expectation = expectation(description: "Async completes")
// When
Task {
await sut.asyncOperation()
expectation.fulfill()
}
// Then
await fulfillment(of: [expectation], timeout: 1.0)
}
// ✅ Swift async/await
func testAsyncFetch_returnsUsers() async throws {
let users = try await sut.fetchUsers()
XCTAssertFalse(users.isEmpty)
}
Property Testing
func testUserValidation_validEmails() {
let validEmails = [
"[email protected]",
"[email protected]",
"[email protected]"
]
for email in validEmails {
XCTAssertTrue(Validator.isValidEmail(email), "Failed for: \(email)")
}
}
func testUserValidation_invalidEmails() {
let invalidEmails = [
"invalid",
"@example.com",
"user@",
"user @example.com"
]
for email in invalidEmails {
XCTAssertFalse(Validator.isValidEmail(email), "Passed for: \(email)")
}
}
UI Tests
Basic UI Test
import XCTest
class LoginUITests: XCTestCase {
var app: XCUIApplication!
override func setUp() {
super.setUp()
continueAfterFailure = false
app = XCUIApplication()
app.launchArguments = ["--uitesting"]
app.launch()
}
func testLoginButton_enabledWhenFieldsFilled() {
// When
let emailField = app.textFields["emailField"]
let passwordField = app.secureTextFields["passwordField"]
let loginButton = app.buttons["loginButton"]
// Then
XCTAssertFalse(loginButton.isEnabled)
// When
emailField.tap()
emailField.typeText("[email protected]")
passwordField.tap()
passwordField.typeText("password123")
// Then
XCTAssertTrue(loginButton.isEnabled)
}
func testSuccessfulLogin_navigatesToHome() {
// Given
app.textFields["emailField"].tap()
app.textFields["emailField"].typeText("[email protected]")
app.secureTextFields["passwordField"].tap()
app.secureTextFields["passwordField"].typeText("password123")
// When
app.buttons["loginButton"].tap()
// Then
XCTAssertTrue(app.staticTexts["welcomeMessage"].exists)
}
}
Snapshot Testing
// ✅ Using View snapshot testing
import SwiftUI
import XCTest
struct UserCard_Previews: PreviewProvider {
static var previews: some View {
UserCard(user: .mock)
}
}
// Snapshot test
func testUserCard_snapshot() {
let view = UserCard(user: .mock)
assertSnapshot(matching: view, as: .image(layout: .sizeThatFits))
}
Testing SwiftUI Views
import XCTest
import ViewInspector // https://github.com/nalexn/ViewInspector
class UserRowTests: XCTestCase {
func testUserRow_displaysUserName() throws {
let user = User(id: "1", name: "John Doe", email: "[email protected]")
let view = UserRow(user: user)
let text = try view.inspect().find(ViewType.Text.self).string()
XCTAssertEqual(text, "John Doe")
}
func testUserRow_tapping_callsAction() throws {
let user = User(id: "1", name: "John", email: "[email protected]")
var tapped = false
let view = UserRow(user: user, onTap: { tapped = true })
try view.inspect().find(ViewType.Button.self).tap()
XCTAssertTrue(tapped)
}
}
Test Helpers
// ✅ Test doubles
extension User {
static let mock1 = User(id: "1", name: "John", email: "[email protected]")
static let mock2 = User(id: "2", name: "Jane", email: "[email protected]")
}
// ✅ Wait helpers
func waitForElementToExist(
_ element: XCUIElement,
timeout: TimeInterval = 5.0
) -> Bool {
let existsPredicate = NSPredicate(format: "exists == true")
let expectation = expectation(for: existsPredicate, evaluatedWith: element)
let result = XCTWaiter.wait(for: [expectation], timeout: timeout)
return result == .completed
}
Performance Tests
func testUserParsing_performance() {
let jsonData = largeUserJSONData
measure {
_ = try? JSONDecoder().decode([User].self, from: jsonData)
}
}
Test Organization
Tests/
├── Unit/
│ ├── ViewModels/
│ │ ├── HomeViewModelTests.swift
│ │ └── ProfileViewModelTests.swift
│ ├── Services/
│ │ ├── UserServiceTests.swift
│ │ └── NetworkServiceTests.swift
│ └── Models/
│ └── UserTests.swift
├── UI/
│ ├── LoginUITests.swift
│ └── HomeUITests.swift
└── Mocks/
├── MockUserService.swift
└── MockNetworkService.swift
Remember: Tests are your safety net. Write them first (TDD) or alongside your code, but always maintain them.