smithery/gmliao

swift-testing-guidelines

Use when writing tests for Swift StateTree - ensures proper Swift Testing framework usage

Installation

$ npx skills add smithery/gmliao --skill swift-testing-guidelines

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from smithery/gmliao.

npx skills add smithery/gmliao

Browse all from smithery/gmliao

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 7,651 B
  • docs SUMMARY.md 121 B

History

  1. First recorded snapshot · 0 installs

SKILL.md

Swift Testing Guidelines

Overview

Guidelines for writing tests using Swift Testing framework (Swift 6's new testing framework, not XCTest).

Announce at start: "I'm using the swift-testing-guidelines skill to write proper tests."

When to Use

  • Writing unit tests
  • Adding tests for new features
  • Reviewing test code
  • Ensuring test quality

Framework: Swift Testing

Important: Swift StateTree uses Swift Testing (Swift 6's new testing framework), NOT XCTest.

Key Differences from XCTest

  • Use @Test attribute instead of func test...()
  • Use #expect() instead of XCTAssert*
  • Use Issue.record() for test failures
  • Test functions can have descriptive names

Test Module Organization

Tests are organized by module:

  • SwiftStateTreeTests: Core library tests
  • SwiftStateTreeTransportTests: Transport layer tests
  • SwiftStateTreeNIOTests: NIO hosting (WebSocket, LandHost)
  • SwiftStateTreeMacrosTests: Macro tests
  • SwiftStateTreeDeterministicMathTests: Deterministic math tests

Test File Structure

File Naming

  • Test files should be suffixed with *Tests.swift
  • Match the type under test (e.g., StateTreeTests.swift)

Test Function Structure

Basic test:

import Testing

@Test("Description of what is being tested")
func testBasicFunctionality() {
    let result = functionUnderTest()
    #expect(result == expectedValue)
}

Test with setup/teardown:

@Test("Test with setup and assertions")
func testWithSetup() {
    // Arrange
    let input = createTestInput()
    
    // Act
    let result = functionUnderTest(input)
    
    // Assert
    #expect(result.isValid)
    #expect(result.value == expectedValue)
}

Test Attributes

@Test Attribute

Basic usage:

@Test
func testSomething() {
    // ...
}

With description:

@Test("Verifies that state sync works correctly")
func testStateSync() {
    // ...
}

With arguments:

@Test(arguments: [1, 2, 3, 4, 5])
func testWithNumber(_ number: Int) {
    #expect(number > 0)
}

Expectations

Basic expectation:

#expect(condition)

Equality:

#expect(actual == expected)

Inequality:

#expect(actual != expected)

Comparison:

#expect(value > threshold)
#expect(value < limit)

Optional unwrapping:

#expect(optional != nil)
let value = #require(optional)  // Unwraps or fails test

Issue Recording

Record test failure:

if condition {
    Issue.record("Condition not met: \(reason)")
}

Arrange-Act-Assert Pattern

Structure tests with clear sections:

@Test("Test state update propagation")
func testStateUpdate() {
    // Arrange
    let initialState = createInitialState()
    let syncEngine = SyncEngine()
    
    // Act
    let update = try syncEngine.generateDiff(
        for: playerID,
        from: initialState
    )
    
    // Assert
    #expect(update.patches.count > 0)
    #expect(update.type == .diff)
}

Test Organization

Group Related Tests

struct StateTreeTests {
    @Test("Initial state is empty")
    func testInitialState() {
        // ...
    }
    
    @Test("State update creates patches")
    func testStateUpdate() {
        // ...
    }
    
    @Test("State sync includes all fields")
    func testStateSync() {
        // ...
    }
}

Use Descriptive Names

Good:

@Test("State sync includes broadcast fields for all players")
func testBroadcastFieldsIncluded() {
    // ...
}

Bad:

@Test
func test1() {
    // ...
}

Test Coverage Requirements

When Adding Tests

  • Public APIs: Always add tests
  • Core game logic: Always add tests
  • Concurrency paths: Aim to cover
  • Edge cases: Include when relevant

Before Submitting PRs

  • ✅ All swift test must pass
  • ✅ All E2E tests must pass
  • ✅ No linter errors
  • ✅ Code comments in English

Running Tests

Run All Tests

swift test

Run Specific Test

swift test --filter StateTreeTests.testGetSyncFields

List All Tests

swift test list

Run Tests in Release Mode

swift test -c release

Test Best Practices

1. Keep Tests Independent

  • Don't share mutable state between tests
  • Each test should be able to run in isolation
  • Use setup/teardown if needed

2. Test One Thing

  • Each test should verify one behavior
  • If testing multiple things, use multiple tests
  • Keep tests focused and clear

3. Use Descriptive Assertions

Good:

#expect(result.count == expectedCount, "Expected \(expectedCount) items, got \(result.count)")

Bad:

#expect(result.count == expectedCount)

4. Test Edge Cases

  • Empty inputs
  • Nil values
  • Boundary conditions
  • Error conditions

5. Avoid Test Implementation Details

  • Test public APIs, not internal methods
  • Focus on behavior, not implementation
  • Don't test private methods directly

Common Patterns

Testing Async Code

@Test("Async operation completes")
func testAsyncOperation() async throws {
    let result = try await asyncFunction()
    #expect(result != nil)
}

Testing Throwing Functions

@Test("Function throws on invalid input")
func testThrowsOnInvalidInput() {
    #expect(throws: SomeError.self) {
        try functionThatThrows(invalidInput)
    }
}

Testing Collections

@Test("Collection contains expected items")
func testCollection() {
    let items = createItems()
    #expect(items.count == 3)
    #expect(items.contains(expectedItem))
}

Integration with Other Testing

WebClient Tests

Location: Examples/Demo/WebClient

Command:

cd Examples/Demo/WebClient && npm test

Framework: Vitest (for Vue component and business logic tests)

E2E Tests

Location: Tools/CLI

Basic E2E (DemoServer):

cd Tools/CLI && ./test-e2e-ci.sh   # Recommended: auto server + all encodings
cd Tools/CLI && npm test           # Requires DemoServer running

Matchmaking E2E (Control Plane + GameServer):

cd Tools/CLI && npm run test:e2e:game:matchmaking:full   # Direct, no LB
cd Tools/CLI && npm run test:e2e:game:matchmaking:nginx # With nginx LB (requires Docker)

See: SwiftStateTree/run-e2e-tests skill for details

Test Anti-Patterns to Avoid

❌ Don't Use XCTest

// ❌ DON'T DO THIS
import XCTest

class StateTreeTests: XCTestCase {
    func testSomething() {
        XCTAssertEqual(actual, expected)
    }
}

❌ Don't Share Mutable State

// ❌ DON'T DO THIS
var sharedState = State()

@Test
func test1() {
    sharedState.value = 1  // Affects other tests!
}

@Test
func test2() {
    #expect(sharedState.value == 0)  // May fail due to test1
}

❌ Don't Test Implementation Details

// ❌ DON'T DO THIS
@Test
func testInternalMethod() {
    let result = object.internalMethod()  // Testing private API
    #expect(result == expected)
}

Code Review Checklist

When reviewing test code:

  • Uses @Test attribute (not XCTest)
  • Uses #expect() for assertions
  • Test file named *Tests.swift
  • Tests organized by type under test
  • Descriptive test names
  • Arrange-Act-Assert structure
  • No shared mutable state
  • Tests are independent
  • Edge cases covered
  • Public APIs tested