Testing strategies for SwiftUI applications including unit tests, UI tests, and preview-based testing.
Generates comprehensive testing strategies for SwiftUI applications including unit tests, UI tests, and preview-based testing.
npx claudepluginhub arustydev/aiThis skill inherits all available tools. When active, it can use any tool Claude has access to.
Testing strategies for SwiftUI applications including unit tests, UI tests, and preview-based testing.
import XCTest
@testable import MyApp
final class ItemViewModelTests: XCTestCase {
var sut: ItemViewModel!
var mockRepository: MockItemRepository!
override func setUp() {
super.setUp()
mockRepository = MockItemRepository()
sut = ItemViewModel(repository: mockRepository)
}
override func tearDown() {
sut = nil
mockRepository = nil
super.tearDown()
}
func test_loadItems_success_populatesItems() async {
// Given
let expectedItems = [Item(id: "1", name: "Test")]
mockRepository.items = expectedItems
// When
await sut.loadItems()
// Then
XCTAssertEqual(sut.items, expectedItems)
XCTAssertFalse(sut.isLoading)
XCTAssertNil(sut.errorMessage)
}
func test_loadItems_failure_setsError() async {
// Given
mockRepository.shouldFail = true
// When
await sut.loadItems()
// Then
XCTAssertTrue(sut.items.isEmpty)
XCTAssertNotNil(sut.errorMessage)
}
}
import XCTest
@testable import MyApp
final class ShoppingCartTests: XCTestCase {
func test_addProduct_increasesQuantity() {
// Given
let cart = ShoppingCart()
let product = Product(id: "1", name: "Widget", price: 9.99)
// When
cart.add(product)
cart.add(product)
// Then
XCTAssertEqual(cart.items.count, 1)
XCTAssertEqual(cart.items.first?.quantity, 2)
}
func test_total_calculatesCorrectly() {
// Given
let cart = ShoppingCart()
cart.items = [
CartItem(productId: "1", price: 10.00, quantity: 2),
CartItem(productId: "2", price: 5.00, quantity: 1)
]
// Then
XCTAssertEqual(cart.total, 25.00)
}
}
func test_fetchData_withCancellation() async {
// Given
let viewModel = DataViewModel()
let task = Task {
await viewModel.fetchData()
}
// When
task.cancel()
await task.value
// Then
XCTAssertTrue(Task.isCancelled)
}
func test_fetchData_withTimeout() async throws {
// Given
let viewModel = SlowViewModel()
// When/Then - should complete within 5 seconds
try await withTimeout(seconds: 5) {
await viewModel.fetchData()
}
}
// Helper
func withTimeout<T>(seconds: Double, operation: @escaping () async throws -> T) async throws -> T {
try await withThrowingTaskGroup(of: T.self) { group in
group.addTask {
try await operation()
}
group.addTask {
try await Task.sleep(for: .seconds(seconds))
throw TimeoutError()
}
let result = try await group.next()!
group.cancelAll()
return result
}
}
import XCTest
final class ItemListUITests: XCTestCase {
var app: XCUIApplication!
override func setUp() {
super.setUp()
continueAfterFailure = false
app = XCUIApplication()
app.launchArguments = ["--uitesting"]
app.launch()
}
func test_addItem_appearsInList() {
// Given
let addButton = app.buttons["addItemButton"]
let nameField = app.textFields["itemNameField"]
let saveButton = app.buttons["saveButton"]
// When
addButton.tap()
nameField.tap()
nameField.typeText("New Item")
saveButton.tap()
// Then
let newItem = app.staticTexts["New Item"]
XCTAssertTrue(newItem.waitForExistence(timeout: 2))
}
}
// Page object for Item List screen
struct ItemListPage {
let app: XCUIApplication
var addButton: XCUIElement {
app.buttons["addItemButton"]
}
var itemList: XCUIElement {
app.collectionViews["itemList"]
}
func item(named name: String) -> XCUIElement {
app.staticTexts[name]
}
func tapAdd() -> AddItemPage {
addButton.tap()
return AddItemPage(app: app)
}
func waitForItems() {
_ = itemList.waitForExistence(timeout: 5)
}
}
// Page object for Add Item screen
struct AddItemPage {
let app: XCUIApplication
var nameField: XCUIElement {
app.textFields["itemNameField"]
}
var saveButton: XCUIElement {
app.buttons["saveButton"]
}
func enterName(_ name: String) -> Self {
nameField.tap()
nameField.typeText(name)
return self
}
func save() -> ItemListPage {
saveButton.tap()
return ItemListPage(app: app)
}
}
// Usage in test
func test_addItem_pageObject() {
let listPage = ItemListPage(app: app)
listPage.waitForItems()
let result = listPage
.tapAdd()
.enterName("New Item")
.save()
XCTAssertTrue(result.item(named: "New Item").exists)
}
// In SwiftUI View
struct ItemRow: View {
let item: Item
var body: some View {
HStack {
Text(item.name)
Spacer()
Button("Delete") {
// delete action
}
.accessibilityIdentifier("deleteButton-\(item.id)")
}
.accessibilityIdentifier("itemRow-\(item.id)")
}
}
// In UI Test
func test_deleteItem() {
let deleteButton = app.buttons["deleteButton-item123"]
deleteButton.tap()
let itemRow = app.otherElements["itemRow-item123"]
XCTAssertFalse(itemRow.exists)
}
import XCTest
import ViewInspector
@testable import MyApp
extension ItemRow: Inspectable {}
final class ItemRowTests: XCTestCase {
func test_displaysItemName() throws {
// Given
let item = Item(id: "1", name: "Test Item")
let view = ItemRow(item: item)
// When
let text = try view.inspect().find(text: "Test Item")
// Then
XCTAssertNotNil(text)
}
func test_deleteButton_callsOnDelete() throws {
// Given
var deleteCalled = false
let item = Item(id: "1", name: "Test")
let view = ItemRow(item: item, onDelete: { deleteCalled = true })
// When
try view.inspect().find(button: "Delete").tap()
// Then
XCTAssertTrue(deleteCalled)
}
}
import XCTest
import SnapshotTesting
@testable import MyApp
final class ItemRowSnapshotTests: XCTestCase {
func test_itemRow_lightMode() {
let view = ItemRow(item: .preview)
.frame(width: 300)
.environment(\.colorScheme, .light)
assertSnapshot(of: view, as: .image)
}
func test_itemRow_darkMode() {
let view = ItemRow(item: .preview)
.frame(width: 300)
.environment(\.colorScheme, .dark)
assertSnapshot(of: view, as: .image)
}
func test_itemRow_dynamicType() {
for category in [ContentSizeCategory.extraSmall, .large, .accessibilityExtraExtraLarge] {
let view = ItemRow(item: .preview)
.frame(width: 300)
.environment(\.sizeCategory, category)
assertSnapshot(of: view, as: .image, named: "\(category)")
}
}
}
// Sample data for previews
extension Item {
static var preview: Item {
Item(id: "preview", name: "Preview Item", description: "A sample item")
}
static var previews: [Item] {
[
Item(id: "1", name: "First Item"),
Item(id: "2", name: "Second Item"),
Item(id: "3", name: "Third Item with a very long name that might wrap")
]
}
}
// Comprehensive preview
#Preview("Item Row - Standard") {
ItemRow(item: .preview)
}
#Preview("Item Row - Long Name") {
ItemRow(item: Item(id: "1", name: "This is a very long item name"))
}
#Preview("Item Row - Dark Mode") {
ItemRow(item: .preview)
.preferredColorScheme(.dark)
}
#Preview("Item List - Multiple") {
List(Item.previews) { item in
ItemRow(item: item)
}
}
Tests/
├── UnitTests/
│ ├── ViewModels/
│ │ └── ItemViewModelTests.swift
│ ├── Models/
│ │ └── ItemTests.swift
│ └── Services/
│ └── ItemRepositoryTests.swift
├── UITests/
│ ├── Pages/
│ │ ├── ItemListPage.swift
│ │ └── AddItemPage.swift
│ └── Flows/
│ └── ItemManagementTests.swift
└── SnapshotTests/
└── ItemRowSnapshotTests.swift
Activates when the user asks about AI prompts, needs prompt templates, wants to search for prompts, or mentions prompts.chat. Use for discovering, retrieving, and improving prompts.
Search, retrieve, and install Agent Skills from the prompts.chat registry using MCP tools. Use when the user asks to find skills, browse skill catalogs, install a skill for Claude, or extend Claude's capabilities with reusable AI agent components.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.