Refactor high-complexity React components in Dify frontend. Use when `pnpm analyze-component...
npx skills add eovidiu/agents-skills --skill "macos-tdd-expert"
Install specific skill from multi-skill repository
# Description
Pragmatic Test-Driven Development skill for macOS engineers building native applications with Swift. Focus on shipping better code faster through behavioral testing, not dogma. Covers XCTest, Swift Testing framework, XCUITest for macOS, protocol-based mocking, MVVM testability, async/await patterns, and Combine testing. Use when writing tests before implementation, testing ViewModels, SwiftUI views, AppKit controllers, services, or setting up testing infrastructure. Emphasizes testing what matters and skipping what doesn't.
# SKILL.md
name: macos-tdd-expert
description: Pragmatic Test-Driven Development skill for macOS engineers building native applications with Swift. Focus on shipping better code faster through behavioral testing, not dogma. Covers XCTest, Swift Testing framework, XCUITest for macOS, protocol-based mocking, MVVM testability, async/await patterns, and Combine testing. Use when writing tests before implementation, testing ViewModels, SwiftUI views, AppKit controllers, services, or setting up testing infrastructure. Emphasizes testing what matters and skipping what doesn't.
Pragmatic TDD for macOS
Overview
Pragmatic macOS TDD Expert provides senior-level guidance for macOS engineers who write tests before implementationβnot because it's dogma, but because it genuinely ships better code faster. This skill focuses on behavior-driven testing using XCTest and Swift Testing, realistic dependency mocking through protocols, and a ViewModel-centric integration approach that maximizes confidence while minimizing brittleness.
Use this skill when building or testing macOS applications with TDD, setting up testing infrastructure, reviewing code for test quality, or needing guidance on what to test (and what to skip).
Core Philosophy
Test to Ship Fast, Not to Reach 100%
Write tests that provide value. Skip tests that don't.
Test what matters:
- ViewModel state transitions and logic
- Model validation and business rules
- Service layer with mocked dependencies
- Error handling and recovery
- Data transformations and formatters
- Async workflows and state machines
- Critical user flows (UI tests, sparingly)
- Accessibility compliance
Skip what doesn't:
- SwiftUI view body composition
- Apple framework internals
- Auto Layout constraints and styling
- Trivial computed properties
- AppKit/SwiftUI rendering details
Reference: references/philosophy.md - Complete pragmatic TDD philosophy, when to test vs. skip, Red-Green-Refactor cycle, and coverage strategies.
The macOS Testing Pyramid
/\
/ \ UI Tests (5-10%) - Critical flows only (XCUITest)
/____\
/ \ Integration Tests (60-70%) - Your sweet spot
/ \ ViewModel + Services + mocked dependencies
/__________\
Unit Tests (20-30%) - Pure functions + models
Focus on integration tests. This is where macOS app value lives.
Reference: references/testing-pyramid.md - Detailed breakdown of unit vs integration vs UI, when to use each layer, and practical distribution examples.
The TDD Cycle
Red β Green β Refactor
1. RED: Write a Failing Test
// Using Swift Testing
@Test func calculatesShippingCost() {
let order = Order(items: [Item(weight: 2.5)], destination: .domestic)
#expect(order.shippingCost == 7.50)
}
// Compilation error - shippingCost doesn't exist yet
2. GREEN: Make It Pass (Minimal Code)
extension Order {
var shippingCost: Double {
let totalWeight = items.reduce(0) { $0 + $1.weight }
return destination == .domestic ? totalWeight * 3.0 : totalWeight * 8.0
}
}
// Test passes
3. REFACTOR: Clean Up (Tests Still Pass)
extension Order {
var shippingCost: Double {
let totalWeight = totalItemWeight
return shippingRate(for: destination) * totalWeight
}
private var totalItemWeight: Double {
items.reduce(0) { $0 + $1.weight }
}
private func shippingRate(for destination: Destination) -> Double {
switch destination {
case .domestic: return 3.0
case .international: return 8.0
}
}
}
// Tests still pass - better code, same behavior
Quick Start
1. Set Up Testing Infrastructure
Use the setup script for SPM projects:
bash scripts/setup-testing.sh
Or manually add a test target in Xcode:
- File β New β Target β macOS Unit Testing Bundle
2. Choose Your Testing Framework
XCTest (mature, Xcode-integrated):
import XCTest
@testable import MyApp
final class UserViewModelTests: XCTestCase {
func testLoadsUsers() async {
// ...
}
}
Swift Testing (modern, expressive):
import Testing
@testable import MyApp
struct UserViewModelTests {
@Test func loadsUsers() async {
// ...
}
}
Reference: references/xctest-testing.md - Complete comparison of XCTest vs Swift Testing, migration guide, and when to use each.
3. Set Up Dependency Injection
Define protocols for external dependencies:
protocol UserServiceProtocol: Sendable {
func fetchUsers() async throws -> [User]
func createUser(_ user: User) async throws -> User
}
Create mock implementations for tests:
final class MockUserService: UserServiceProtocol {
var usersToReturn: [User] = []
var errorToThrow: Error?
func fetchUsers() async throws -> [User] {
if let error = errorToThrow { throw error }
return usersToReturn
}
func createUser(_ user: User) async throws -> User {
if let error = errorToThrow { throw error }
return user
}
}
4. Start Testing!
Use templates for common patterns:
- assets/templates/ViewModelTests.swift - ViewModel testing
- assets/templates/ViewTests.swift - SwiftUI view testing
- assets/templates/ServiceTests.swift - Service/networking layer
- assets/templates/AppKitTests.swift - AppKit controllers
- assets/templates/SnapshotTests.swift - Visual regression
- assets/templates/TestHelpers.swift - Common utilities
When to Use This Skill
Trigger this skill when:
- Writing tests before implementing macOS features (TDD)
- Testing ViewModels and their state transitions
- Testing service layers with mocked dependencies
- Writing XCUITest UI tests for macOS apps
- Testing async/await and Combine code
- Testing AppKit controllers and views
- Setting up testing infrastructure for macOS projects
- Reviewing code for testability
- Deciding what to test vs. skip
- Debugging flaky or failing tests
Testing Patterns by Use Case
1. ViewModel Testing (Integration Layer)
Test logic through ViewModels - this is your primary testing surface.
@MainActor
final class UserListViewModelTests: XCTestCase {
var mockService: MockUserService!
var viewModel: UserListViewModel!
override func setUp() {
mockService = MockUserService()
viewModel = UserListViewModel(service: mockService)
}
func testLoadsAndDisplaysUsers() async {
mockService.usersToReturn = [
User(id: "1", name: "Alice"),
User(id: "2", name: "Bob")
]
await viewModel.loadUsers()
XCTAssertEqual(viewModel.users.count, 2)
XCTAssertEqual(viewModel.users.first?.name, "Alice")
XCTAssertFalse(viewModel.isLoading)
XCTAssertNil(viewModel.errorMessage)
}
func testShowsErrorOnFailure() async {
mockService.errorToThrow = ServiceError.networkUnavailable
await viewModel.loadUsers()
XCTAssertTrue(viewModel.users.isEmpty)
XCTAssertNotNil(viewModel.errorMessage)
XCTAssertFalse(viewModel.isLoading)
}
}
Reference: references/xctest-testing.md - XCTest and Swift Testing patterns for ViewModels.
Template: assets/templates/ViewModelTests.swift
2. Service Layer Testing
Mock the network, test real service logic.
final class UserServiceTests: XCTestCase {
func testFetchUsersDecodesResponse() async throws {
let mockSession = MockURLSession(
data: usersJSON.data(using: .utf8)!,
response: HTTPURLResponse(statusCode: 200)
)
let service = UserService(session: mockSession)
let users = try await service.fetchUsers()
XCTAssertEqual(users.count, 2)
XCTAssertEqual(users.first?.name, "Alice")
}
func testFetchUsersThrowsOnServerError() async {
let mockSession = MockURLSession(
data: Data(),
response: HTTPURLResponse(statusCode: 500)
)
let service = UserService(session: mockSession)
do {
_ = try await service.fetchUsers()
XCTFail("Expected error")
} catch {
XCTAssertTrue(error is ServiceError)
}
}
}
Reference: references/mocking-strategies.md - Protocol mocking, URLProtocol stubs, and dependency injection patterns.
Template: assets/templates/ServiceTests.swift
3. XCUITest for macOS
Test critical user flows through the actual UI.
final class LoginUITests: XCTestCase {
let app = XCUIApplication()
override func setUp() {
continueAfterFailure = false
app.launchArguments = ["--ui-testing"]
app.launch()
}
func testLoginFlowShowsDashboard() {
let emailField = app.textFields["Email"]
emailField.click()
emailField.typeText("[email protected]")
let passwordField = app.secureTextFields["Password"]
passwordField.click()
passwordField.typeText("password123")
app.buttons["Sign In"].click()
XCTAssertTrue(
app.staticTexts["Welcome, Alice"].waitForExistence(timeout: 5)
)
}
}
Reference: references/ui-testing.md - XCUITest patterns for macOS including menus, windows, sheets, and keyboard shortcuts.
Template: assets/templates/ViewTests.swift
4. Async/Await and Combine Testing
Test asynchronous code with structured concurrency.
@MainActor
func testDebounceSearch() async {
let mockService = MockSearchService()
let viewModel = SearchViewModel(service: mockService)
viewModel.searchText = "Swi"
viewModel.searchText = "Swif"
viewModel.searchText = "Swift"
// Wait for debounce
try? await Task.sleep(for: .milliseconds(400))
XCTAssertEqual(mockService.searchCallCount, 1)
XCTAssertEqual(mockService.lastQuery, "Swift")
}
Template: assets/templates/ViewModelTests.swift
5. AppKit Controller Testing
Test NSViewControllers and NSWindowControllers.
final class PreferencesControllerTests: XCTestCase {
func testDisplaysCurrentSettings() {
let mockSettings = MockSettingsStore(theme: .dark, fontSize: 14)
let controller = PreferencesViewController(settings: mockSettings)
controller.loadView()
controller.viewDidLoad()
XCTAssertEqual(controller.themePopUp.titleOfSelectedItem, "Dark")
XCTAssertEqual(controller.fontSizeSlider.integerValue, 14)
}
}
Template: assets/templates/AppKitTests.swift
Key Principles
1. Test Behavior, Not Implementation
// BAD - Tests internal state
XCTAssertTrue(viewModel.internalFlag)
// GOOD - Tests observable behavior
XCTAssertEqual(viewModel.displayTitle, "2 items in cart")
2. Mock Externally, Integrate Internally
// GOOD - Mock external dependency via protocol
let mockService = MockUserService(users: testUsers)
let viewModel = UserListViewModel(service: mockService)
// BAD - Mock your own ViewModel
let mockViewModel = MockUserListViewModel() // Don't do this
3. Use Accessibility Identifiers, Not View Hierarchy
// GOOD - Stable identifier
app.buttons["submitOrder"]
// GOOD - Semantic label
app.buttons["Place Order"]
// BAD - Fragile hierarchy traversal
app.windows.firstMatch.groups.element(boundBy: 2).buttons.firstMatch
4. Use async/await, Not Expectations (When Possible)
// GOOD - Direct async
@MainActor
func testLoadsData() async {
await viewModel.loadData()
XCTAssertEqual(viewModel.items.count, 3)
}
// OK - XCTestExpectation for callback-based APIs
func testLegacyCallback() {
let expectation = expectation(description: "Callback fired")
legacyAPI.fetch { result in
// assert...
expectation.fulfill()
}
wait(for: [expectation], timeout: 5)
}
5. Write Minimal Code to Pass
Let tests drive implementation incrementally.
// First test
@Test func emptyCartHasZeroTotal() {
let cart = Cart(items: [])
#expect(cart.total == 0)
}
// Minimal implementation
struct Cart {
let items: [Item]
var total: Double { 0 } // Good enough for now
}
// Next test drives real logic
@Test func cartSumsItemPrices() {
let cart = Cart(items: [Item(price: 10), Item(price: 20)])
#expect(cart.total == 30)
}
// Now implement
struct Cart {
let items: [Item]
var total: Double { items.reduce(0) { $0 + $1.price } }
}
Anti-Patterns to Avoid
Testing SwiftUI View Body
// BAD - Inspecting view hierarchy
let view = ContentView()
// Trying to assert on body structure
// GOOD - Test the ViewModel that drives the view
let vm = ContentViewModel()
await vm.load()
XCTAssertEqual(vm.title, "Dashboard")
Testing Apple Frameworks
// BAD
func testNavigationStackPushesView() { /* ... */ }
func testStatePropertyWrapperUpdates() { /* ... */ }
func testCombinePublisherEmits() { /* ... */ }
// GOOD - Test YOUR logic
func testViewModelTransitionsToDetailState() async { /* ... */ }
Using Sleep Instead of Expectations
// BAD
Thread.sleep(forTimeInterval: 2.0)
// GOOD
await fulfillment(of: [expectation], timeout: 5)
// GOOD
try await Task.sleep(for: .milliseconds(100))
await viewModel.loadData()
Giant Test Methods
// BAD - 200 lines testing everything
func testCompleteUserJourney() { /* ... */ }
// GOOD - Focused tests
func testUserCanLogIn() async { /* ... */ }
func testUserCanBrowseProducts() async { /* ... */ }
func testUserCanCheckout() async { /* ... */ }
Speed Matters
Fast tests = fast feedback = fast iteration.
Optimize for speed:
- Unit tests: <50ms each
- ViewModel integration tests: <200ms each
- XCUITest: <10 seconds each
Run in parallel:
# Xcode parallel testing
xcodebuild test -scheme MyApp -parallel-testing-enabled YES
# SPM parallel testing (default)
swift test --parallel
Resources
references/
Comprehensive documentation loaded as needed:
philosophy.md- When to test, what to skip, Red-Green-Refactor, coverage strategiestesting-pyramid.md- Unit vs integration vs UI distribution and examplesxctest-testing.md- XCTest and Swift Testing framework patternsui-testing.md- XCUITest for macOS: windows, menus, sheets, popoverspatterns.md- Common patterns, anti-patterns, best practices checklistmocking-strategies.md- Protocol mocking, URLProtocol, dependency injection
scripts/
Setup utilities:
setup-testing.sh- Set up test targets and dependencies
assets/templates/
Copy-paste templates for common scenarios:
ViewModelTests.swift- ViewModel testing with mocked servicesViewTests.swift- SwiftUI view testing approachesServiceTests.swift- Service/networking layer with URLProtocolAppKitTests.swift- AppKit NSViewController testingSnapshotTests.swift- Visual regression with SwiftUI previewsTestHelpers.swift- Common test utilities and factories
Decision Tree: What to Test
Is it a pure function or model logic?
ββ YES β Unit test
ββ NO β
Does it involve ViewModel state transitions or service integration?
ββ YES β Integration test (ViewModel + mocked services)
ββ NO β
Is it a critical user flow spanning multiple screens?
ββ YES β XCUITest UI test
ββ NO β Probably don't test it
Getting Help
For specific topics:
- Philosophy: When to test vs. skip β
references/philosophy.md - XCTest/Swift Testing: Framework patterns β
references/xctest-testing.md - UI Testing: XCUITest for macOS β
references/ui-testing.md - Mocking: Protocols and DI β
references/mocking-strategies.md - Test strategy: Unit vs integration vs UI β
references/testing-pyramid.md - Common patterns: Best practices β
references/patterns.md - Setup project: Run
scripts/setup-testing.sh - Templates: Copy from
assets/templates/
Summary
TDD is a tool for shipping better macOS apps faster.
- 70% integration tests - ViewModels + services with mocked dependencies
- 25% unit tests - Pure functions + model logic
- 5% UI tests - Critical flows with XCUITest
Test behavior, not implementation.
Mock externally, integrate internally.
Speed is a feature.
Write tests that make you ship with confidence, not tests that make you scared to refactor.
# Supported AI Coding Agents
This skill is compatible with the SKILL.md standard and works with all major AI coding agents:
Learn more about the SKILL.md standard and how to use these skills with your preferred AI coding agent.