agentleFS
ShopifyKnown publisherShopify/checkout-kit/platforms/swift/.cursor/rules/swift-test.mdc

checkout-kit / rules

Cursor rule7 starsChanged 24 days agoMITView on GitHub

Swift testing best practices and requirements

1 other project uses a version of this.

Adopted by

Most recently taken up by

  • Other versions1 project uses a version of this.

    Word for word · 1

    1. Shopify/checkout-sheet-kit-swift★ 66100% same

    Edited · 0

    None found.

    Wrote their own version · 0

    None found.

    How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.

  • Where files are headingNew Cursor rule files have more rules about planning and tracking tasks, and fewer about code style.

    Of the Cursor rule files we read, the 4,157 first seen in the last six months, against the older ones: the kind of rule that gained most share, and the kind that lost most. About files like this in general, not this one, and an estimate, since a kind is a pattern in the words.

  • In this repository3 other files it gives its agents.

The file

Show its rules and text

We split this swift-test.mdc into the rules it gives an agent

16 rules. About 0 are common, 14 are distinctive, and 2 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈14

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 2

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Create separate test methods for each test case

    line 12
  2. Very specific:

    Use guard statements with XCTFail for unwrapping optionals

    line 13
  3. Distinctive:

    Write focused tests that test one thing at a time

    line 23
  4. Very specific:

    Use descriptive test method names in the format test_<methodName>_<withCircumstances>_<shouldExpectation>

    line 24
  5. Distinctive:

    If a function may throw multiple types of errors, write multiple tests to capture them in isolation

    line 30
  6. Distinctive:

    Use typed catches when testing throwing expressions

    line 75
  7. Distinctive:

    Delete and recreate test files when debugging

    line 76
  8. Distinctive:

    Add boilerplate comments like "// Given", "// When", "// Then"

    line 77
  9. Distinctive:

    Use comments ONLY to explain non-obvious side effects or complex reasoning

    line 81
  10. Distinctive:

    Keep comments minimal and purposeful

    line 82
  11. Distinctive:

    Always import both ViewInspector and your module under test:

    line 91
  12. Distinctive:

    Test leaf components first (basic views like Text, Image, Button)

    line 185
  13. Distinctive:

    Use focused searches with specific view types when possible

    line 308
  14. Distinctive:

    Avoid deep view hierarchy traversal in performance-critical tests

    line 309
  15. Distinctive:

    Consider grouping related view tests in the same test class for better organization

    line 310
  16. Distinctive:

    Use accessibility identifiers when direct property inspection isn't available

    line 336

≈ “Common” is an estimate: at least 1 in 5 of the 6,668 Cursor rule files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
swift-test.mdc
---
description: Swift testing best practices and requirements
globs: ["Tests/**/*.swift"]
alwaysApply: true
---

# SWIFT TESTING RULES

## Test Structure Requirements

### DO:
- Create separate test methods for each test case
- Use `guard` statements with `XCTFail` for unwrapping optionals
```swift
func testSomething() {
    guard let result = someOptionalValue else {
        XCTFail("Expected non-nil value")
        return
    }
    XCTAssertEqual(result, expectedValue)
}
```
- Write focused tests that test one thing at a time
- Use descriptive test method names in the format `test_<methodName>_<withCircumstances>_<shouldExpectation>`
```
    func test_canTransition_fromAppleSheetPresentedState_shouldAllowPaymentAuthorizationAndInterruptAndCompleted() 
    func test_ensureCurrencyNotChanged_withNoInitialCurrency_shouldNotThrow() 
```

- If a function may throw multiple types of errors, write multiple tests to capture them in isolation
```
func throwingFunction() {
    if someCondition {
        throw Error.foo
      } else 
      throw Error.bar
  }

  func test_throwingFunction_whenSomeConditionTrue_shouldThrowFoo() {
    do {
        _ = try await storefront.createCart()
        XCTFail("Expected error to be thrown")
    } catch {
      guard case let error = Error.foo else {
        XCTFail("Expected .foo")
      }
    }
  }
  func test_throwingFunction_whenSomeConditionTrue_shouldThrowBar(){
    do {
        _ = try await storefront.createCart()
        XCTFail("Expected error to be thrown")
    } catch {
      guard case let error = Error.foo else {
        XCTFail("Expected .foo")
      }
    }
  }
```

## Code Examples

### ✅ CORRECT: Unwrapping optionals
```swift
func testSomething() {
    guard let result = someOptionalValue else {
        XCTFail("Expected non-nil value")
        return
    }
    XCTAssertEqual(result, expectedValue)
}
```

### DON'T:
- Use typed catches when testing throwing expressions
- Delete and recreate test files when debugging
- Add boilerplate comments like "// Given", "// When", "// Then"

## Comments

- Use comments ONLY to explain non-obvious side effects or complex reasoning
- Keep comments minimal and purposeful
- Focus on WHY something is done, not WHAT is being done

---

# SWIFTUI VIEW TESTING WITH VIEWINSPECTOR

## Setup and Imports

Always import both ViewInspector and your module under test:
```swift
import ViewInspector
import SwiftUI
@testable import YourModule
import XCTest
```

## Basic ViewInspector Patterns

### 1. Simple View Inspection
```swift
func test_textView_shouldDisplayExpectedContent() throws {
    let sut = Text("Hello World")
    let text = try sut.inspect().text().string()
    XCTAssertEqual(text, "Hello World")
}
```

### 2. Finding Views by Type
```swift
func test_buttonExists_shouldFindButton() throws {
    let sut = VStack {
        Text("Title")
        Button("Tap me") { }
    }
    
    XCTAssertNoThrow(try sut.inspect().find(button: "Tap me"))
}
```

### 3. Finding Views with Conditions
```swift
func test_findAllImages_shouldReturnExpectedCount() throws {
    let sut = HStack {
        Image(systemName: "star")
        Image(systemName: "heart")
        Text("Label")
    }
    
    let images = try sut.inspect().findAll(ViewType.Image.self)
    XCTAssertEqual(images.count, 2)
}
```

### 4. Accessing Custom View State
```swift
func test_customView_shouldHaveExpectedState() throws {
    let sut = MyCustomView()
    let customView = try sut.inspect().find(MyCustomView.self).actualView()
    XCTAssertTrue(customView.someProperty)
}
```

### 5. Testing View Modifiers and Attributes
```swift
func test_imageAttributes_shouldHaveCorrectFrame() throws {
    let sut = Image("logo")
        .resizable()
        .frame(height: 24)
    
    let image = try sut.inspect().image()
    // Note: ViewInspector may not expose all modifier properties directly
    // Focus on testing the image name and basic properties
    XCTAssertEqual(try image.actualImage().name(), "logo")
    XCTAssertEqual(try image.fixedHeight(), 24)
}
```

### 6. Testing Button Actions
```swift
func test_buttonTap_shouldTriggerAction() throws {
    var actionCalled = false
    let sut = Button("Tap") { actionCalled = true }
    
    try sut.inspect().button().tap()
    XCTAssertTrue(actionCalled)
}
```

### 7. Environment and Context Testing
```swift
func test_viewWithEnvironment_shouldReceiveCorrectValue() throws {
    let sut = MyView()
        .environment(\.colorScheme, .dark)
    
    let view = try sut.inspect().find(MyView.self).actualView()
    // Test that view responds to dark mode
}
```

## SwiftUI View Testing Best Practices

### Structure Tests by Component Hierarchy
- Test leaf components first (basic views like Text, Image, Button)
- Then test container views that compose those components
- Finally test full screen views and navigation flows

### Test View State, Not Implementation
```swift
// ✅ GOOD: Test what the user sees
func test_loginButton_whenUserNotLoggedIn_shouldShowLoginText() throws {
    let sut = LoginView(isLoggedIn: false)
    let buttonText = try sut.inspect().find(button: "Log In").labelView().text().string()
    XCTAssertEqual(buttonText, "Log In")
}

// ❌ BAD: Test internal implementation details
func test_loginView_shouldHaveLoginButtonProperty() {
    let sut = LoginView(isLoggedIn: false)
    XCTAssertNotNil(sut.loginButton) // Don't test internal properties
}
```

### Handle View State Changes
```swift
func test_toggleButton_shouldChangeState() throws {
    @State var isOn = false
    let sut = Toggle("Setting", isOn: $isOn)
    
    try sut.inspect().toggle().tap()
    XCTAssertTrue(isOn)
}
```

### Testing Conditional Views
```swift
func test_conditionalView_whenConditionTrue_shouldShowContent() throws {
    let sut = ConditionalView(showContent: true)
    
    XCTAssertNoThrow(try sut.inspect().find(text: "Content"))
}

func test_conditionalView_whenConditionFalse_shouldHideContent() throws {
    let sut = ConditionalView(showContent: false)
    
    XCTAssertThrowsError(try sut.inspect().find(text: "Content"))
}
```

## Common ViewInspector Patterns

### Finding Nested Views
```swift
func test_nestedView_shouldFindDeepContent() throws {
    let sut = VStack {
        HStack {
            VStack {
                Text("Deep content")
            }
        }
    }
    
    let text = try sut.inspect().vStack().hStack(0).vStack(0).text(0).string()
    XCTAssertEqual(text, "Deep content")
}
```

### Testing Lists and ForEach
```swift
func test_list_shouldContainExpectedItems() throws {
    let items = ["Item 1", "Item 2", "Item 3"]
    let sut = List(items, id: \.self) { item in
        Text(item)
    }
    
    let list = try sut.inspect().list()
    XCTAssertEqual(list.count, 3)
    
    for (index, expectedItem) in items.enumerated() {
        let text = try list[index].text().string()
        XCTAssertEqual(text, expectedItem)
    }
}
```

### Testing Navigation and Sheets
```swift
func test_navigationLink_shouldNavigateToDestination() throws {
    let sut = NavigationLink("Go to Detail", destination: DetailView())
    
    let destination = try sut.inspect().navigationLink().destination()
    XCTAssertNoThrow(try destination.find(DetailView.self))
}
```

## Error Handling in Tests

### Graceful Error Handling
```swift
func test_viewInspection_shouldHandleErrors() {
    let sut = SomeView()
    
    do {
        let result = try sut.inspect().find(text: "Expected Text")
        // Test passed - continue with assertions
    } catch {
        XCTFail("Expected to find text but got error: \(error)")
    }
}
```

### Testing for Expected Failures
```swift
func test_hiddenView_shouldNotBeFound() {
    let sut = VStack {
        Text("Visible")
        // Hidden text not included
    }
    
    XCTAssertThrowsError(try sut.inspect().find(text: "Hidden"))
}
```

## Performance Considerations

- ViewInspector uses reflection, so tests may be slower than unit tests
- Use focused searches with specific view types when possible
- Avoid deep view hierarchy traversal in performance-critical tests
- Consider grouping related view tests in the same test class for better organization

## Debugging Tips

### Print View Hierarchy
```swift
func debugViewHierarchy() throws {
    let sut = MyComplexView()
    print(try sut.inspect().description) // Prints the view hierarchy
}
```

### Step-by-step Navigation
```swift
func test_stepByStepNavigation() throws {
    let sut = MyView()
    let container = try sut.inspect().vStack() // Get the container first
    let button = try container.button(0) // Then get the specific child
    try button.tap()
}
```

## ViewInspector Limitations

- Some SwiftUI modifiers may not be directly inspectable
- Focus on testing the essential behavior and visible content
- Use accessibility identifiers when direct property inspection isn't available
- Some complex view hierarchies may require step-by-step navigation

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.