agentsclimarketplace

Testing patterns

Skill 0xMassi/claude-skills/testing-patterns

Twelve Claude Code skills enforcing strict code quality, security, and modern language patterns across TypeScript, Rust, Swift, Go, JavaScript, and PostgreSQL.

Install
npx -y skills add 0xMassi/claude-skills --skill testing-patterns

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 5 stars5 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Testing standards and patterns across TypeScript (Vitest), Rust (cargo test), Swift (Swift Testing), and Go (go test). Covers file conventions, mock strategies, async testing, CI integration, and anti-patterns based on real project usage.

SKILL.md

17.3 KB, as published. Nobody here has run it

Testing Patterns

TypeScript / JavaScript Testing (Vitest)

File Conventions

  • Colocated tests: src/foo.test.ts next to src/foo.ts (primary pattern)
  • E2E tests: *.e2e.test.ts in test/ directory or colocated
  • Live tests: *.live.test.ts for tests requiring real API keys
  • Setup file: test/setup.ts for global beforeEach/afterEach hooks
  • Config layering: base vitest.config.ts extended by vitest.unit.config.ts, vitest.e2e.config.ts, vitest.live.config.ts

Test Structure

import { describe, expect, it, vi } from "vitest";

describe("resolvePermission", () => {
  it("auto-approves safe tools without prompting", async () => {
    const prompt = vi.fn(async () => true);
    const res = await resolvePermission(makeRequest(), { prompt });
    expect(res).toEqual({ outcome: "allow" });
    expect(prompt).not.toHaveBeenCalled();
  });

  it("prompts for dangerous operations", async () => {
    const prompt = vi.fn(async () => true);
    await resolvePermission(makeDangerousRequest(), { prompt });
    expect(prompt).toHaveBeenCalledTimes(1);
    expect(prompt).toHaveBeenCalledWith("exec", "exec: rm -rf /");
  });
});

Mock Patterns

// Function mocks for dependency injection
const sendFn = vi.fn(async () => ({ messageId: "test" }));

// Module mocks (use sparingly -- prefer DI)
vi.mock("./network.js", () => ({
  fetch: vi.fn().mockResolvedValue({ ok: true }),
}));

// Spy on existing methods
const spy = vi.spyOn(store, "save");
expect(spy).toHaveBeenCalledWith(expectedData);

// Stub environment variables (auto-cleaned with unstubEnvs: true)
vi.stubEnv("API_KEY", "test-key-123");

// Fake timers (guard against leaks in afterEach)
vi.useFakeTimers();
vi.advanceTimersByTime(5000);
// Always restore: vi.useRealTimers()

Test Setup Pattern (Global)

// test/setup.ts
import { afterAll, afterEach, beforeEach, vi } from "vitest";
import { withIsolatedTestHome } from "./test-env.js";

process.env.VITEST = "true";
const testEnv = withIsolatedTestHome(); // Isolated HOME per worker
afterAll(() => testEnv.cleanup());

afterEach(() => {
  if (vi.isFakeTimers()) vi.useRealTimers(); // Prevent cross-test leaks
});

Factory Functions for Test Data

// Prefer factory functions over raw object literals
function makePermissionRequest(
  overrides: Partial<PermissionRequest> = {},
): PermissionRequest {
  const base: PermissionRequest = {
    sessionId: "session-1",
    toolCall: { toolCallId: "tool-1", title: "read: src/index.ts", status: "pending" },
    options: [{ kind: "allow_once", optionId: "allow" }],
  };
  return { ...base, ...overrides };
}

Coverage Configuration

// vitest.config.ts
coverage: {
  provider: "v8",
  reporter: ["text", "lcov"],
  all: false, // Only count files exercised by tests
  thresholds: { lines: 70, functions: 70, branches: 55, statements: 70 },
  include: ["./src/**/*.ts"],
  exclude: ["src/**/*.test.ts", "src/cli/**", "src/gateway/**"],
}

Anti-Patterns

  • Do NOT set maxWorkers above 16 (diminishing returns, flaky on CI)
  • Do NOT test implementation details (internal state, private methods)
  • Do NOT use vi.mock when dependency injection via function params works
  • Do NOT leak fake timers across tests -- always restore in afterEach
  • Do NOT skip edge cases: empty input, null, boundary values, error paths

Rust Testing (cargo test)

File Conventions

  • Inline unit tests: #[cfg(test)] mod tests at the bottom of each source file (primary pattern)
  • Integration tests: tests/ directory at crate root (rare in workspace projects)
  • Test-only dependencies: in [dev-dependencies] of each crate's Cargo.toml
  • Run all: cargo test --lib | Single crate: cargo test -p claudio-gateway --lib
  • Filter by name: cargo test --lib test_name_substring
  • Skip slow tests: cargo test --lib -- --skip concurrent

Unit Test Structure

#[cfg(test)]
mod tests {
    use super::*;
    use std::time::Duration;

    #[test]
    fn insert_and_get() {
        let map = BoundedMap::new(10, Duration::from_secs(60));
        assert!(map.insert("a", 1));
        assert_eq!(map.get("a"), Some(1));
    }

    #[test]
    fn capacity_enforced() {
        let map = BoundedMap::new(3, Duration::from_secs(60));
        map.insert("a", 1);
        map.insert("b", 2);
        map.insert("c", 3);
        map.insert("d", 4); // should evict one
        assert!(map.len() <= 3);
    }

    #[test]
    fn ttl_expiry_on_get() {
        let map = BoundedMap::new(10, Duration::from_millis(1));
        map.insert("a", 1);
        std::thread::sleep(Duration::from_millis(5));
        assert_eq!(map.get("a"), None);
    }
}

Async Tests

#[cfg(test)]
mod tests {
    use super::*;
    use std::sync::Arc;

    #[tokio::test]
    async fn returns_immediately_when_no_tool_calls() {
        let provider = Arc::new(MockToolProvider::new(vec![final_response("Hello!")]));
        let mut executor = AgentExecutor::new(provider, "http://unused:9999", 10);

        let result = executor.execute(test_request()).await.unwrap();
        assert_eq!(result.iterations, 1);
        assert_eq!(result.tool_calls_made, 0);
    }

    #[tokio::test]
    async fn max_iterations_produces_final_response() {
        // Provider always returns tool calls -- should hit max iterations
        let provider = Arc::new(MockToolProvider::new(vec![tool_call_response()]));
        let mut executor = AgentExecutor::new(provider, "http://unused:9999", 3);
        let result = executor.execute(test_request()).await.unwrap();
        assert!(result.iterations <= 3);
    }
}

Stub/Mock Providers (Trait-Based Mocking)

/// Stub: always returns a fixed response. No network calls.
pub struct StubProvider { provider_name: String }

#[async_trait]
impl Provider for StubProvider {
    fn name(&self) -> &str { &self.provider_name }
    async fn chat_completion(&self, req: &ChatCompletionRequest)
        -> Result<ChatCompletionResponse, ProviderError> {
        Ok(ChatCompletionResponse { /* canned fields */ })
    }
}

/// Scripted: returns pre-configured responses in sequence.
/// Useful for multi-turn agent loops.
pub struct ScriptedProvider {
    responses: std::sync::Mutex<Vec<ChatCompletionResponse>>,
}

Testing Patterns for Workspace Projects

  • Each crate has its own #[cfg(test)] modules (typical: 100+ test files in a workspace)
  • Shared test utilities live in dedicated stub modules (e.g., providers/src/stub.rs)
  • Use Arc<dyn Trait> for mock injection in async contexts
  • Test crates independently: cargo test -p crate-name --lib

Tauri Command Testing

// Minimal inline tests for Tauri commands
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn is_newer_detects_version_bump() {
        assert!(is_newer("1.0.1", "1.0.0"));
        assert!(!is_newer("1.0.0", "1.0.0"));
        assert!(!is_newer("0.9.9", "1.0.0"));
    }
}

Swift / iOS Testing (Swift Testing)

Framework: Swift Testing (not XCTest)

Projects use the modern Testing framework with @Test macros and #expect assertions.

Test Structure

import Foundation
import Testing
@testable import YourApp

@Suite struct KeychainStoreTests {
    @Test func saveLoadUpdateDeleteRoundTrip() {
        let service = "com.example.tests.\(UUID().uuidString)"
        let account = "value"

        #expect(KeychainStore.delete(service: service, account: account))
        #expect(KeychainStore.loadString(service: service, account: account) == nil)
        #expect(KeychainStore.saveString("first", service: service, account: account))
        #expect(KeychainStore.loadString(service: service, account: account) == "first")
    }
}

Serialized Suites (Shared State)

@Suite(.serialized) struct GatewayConnectionControllerTests {
    @Test @MainActor func resolvedDisplayNameSetsDefaultWhenMissing() {
        withUserDefaults([displayKey: nil, "node.instanceId": "ios-test"]) {
            let appModel = NodeAppModel()
            let controller = GatewayConnectionController(appModel: appModel, startDiscovery: false)
            let resolved = controller._test_resolvedDisplayName(defaults: .standard)
            #expect(!resolved.isEmpty)
        }
    }
}

Async Test Methods

@Suite(.serialized) struct VoiceWakeManagerStateTests {
    @Test @MainActor func suspendAndResumeCycleUpdatesState() async {
        let manager = VoiceWakeManager()
        manager.isEnabled = true
        manager.isListening = true

        let suspended = manager.suspendForExternalAudioCapture()
        #expect(suspended == true)
        #expect(manager.isListening == false)

        manager.resumeAfterExternalAudioCapture(wasSuspended: true)
        try? await Task.sleep(nanoseconds: 900_000_000)
        #expect(manager.statusText.contains("Voice Wake") == true)
    }
}

Testing @Observable ViewModels

  • Instantiate the @Observable class directly in the test
  • Use @MainActor annotation for UI-bound state
  • For UserDefaults-dependent code, use a withUserDefaults helper that snapshots and restores

Test Helper Pattern: Expose Internals

// In production code, expose test-only accessors:
func _test_resolvedDisplayName(defaults: UserDefaults) -> String { ... }
func _test_currentCaps() -> [String] { ... }
func _test_handleRecognitionCallback(transcript: String?, ...) { ... }

Actor-Based Test Capture

actor CaptureBox {
    var value: String?
    func set(_ next: String) { self.value = next }
}

let capture = CaptureBox()
manager.configure { cmd in await capture.set(cmd) }

Build & Test Commands

# Run tests on simulator
xcodebuild test -project App.xcodeproj -scheme App \
  -destination 'platform=iOS Simulator,name=iPhone 17 Pro'

# Build only (catches compile errors)
xcodebuild -project App.xcodeproj -scheme App \
  -destination 'platform=iOS Simulator,name=iPhone 17 Pro' build

Go Testing (go test)

File Conventions

  • Test files: *_test.go in the same package as the code under test
  • Same package access: tests use package crypto (not package crypto_test) for internal access
  • Run: go test ./... | Single package: go test ./internal/crypto/

Standard Test Pattern

package crypto

import (
    "bytes"
    "testing"
)

func TestEncryptDecrypt(t *testing.T) {
    key := make([]byte, chacha20poly1305.KeySize)
    rand.Read(key)
    plaintext := []byte("Hello, World!")

    encrypted, err := Encrypt(plaintext, key, nil)
    if err != nil {
        t.Fatalf("Encrypt failed: %v", err)
    }

    decrypted, err := Decrypt(encrypted, key, nil)
    if err != nil {
        t.Fatalf("Decrypt failed: %v", err)
    }

    if !bytes.Equal(decrypted, plaintext) {
        t.Errorf("Decrypted text doesn't match.\nGot:  %q\nWant: %q", decrypted, plaintext)
    }
}

Error Case Testing

func TestEncryptInvalidKey(t *testing.T) {
    shortKey := make([]byte, 16)
    _, err := Encrypt([]byte("test"), shortKey, nil)
    if err == nil {
        t.Error("Encrypt should reject short key")
    }
}

Integration Tests with Channels

func TestHandshakeClientServer(t *testing.T) {
    serverResult := make(chan *HandshakeResult, 1)
    serverErr := make(chan error, 1)

    server := network.NewServer(handler)
    if err := server.Listen("127.0.0.1:0"); err != nil {
        t.Fatalf("Failed to listen: %v", err)
    }
    go server.Serve()
    defer server.Close()

    conn, err := network.Dial(server.Addr().String())
    if err != nil {
        t.Fatalf("Failed to dial: %v", err)
    }
    defer conn.Close()

    select {
    case err := <-serverErr:
        t.Fatalf("Server handshake failed: %v", err)
    case result := <-serverResult:
        if result.PeerID != "test-client" {
            t.Errorf("Server got peer ID = %q, want %q", result.PeerID, "test-client")
        }
    case <-time.After(2 * time.Second):
        t.Fatal("Server handshake timeout")
    }
}

Uniqueness / Determinism Tests

func TestGenerateKeyUniqueness(t *testing.T) {
    k1, _ := GenerateIdentityKey()
    k2, _ := GenerateIdentityKey()
    if bytes.Equal(k1.PublicKey, k2.PublicKey) {
        t.Error("Generated identical public keys (should be unique)")
    }
}

func TestFingerprintDeterministic(t *testing.T) {
    keypair, _ := GenerateIdentityKey()
    f1 := keypair.Fingerprint()
    f2 := keypair.Fingerprint()
    if f1 != f2 {
        t.Error("Fingerprint is not deterministic")
    }
}

Race Detection

go test -race ./...   # Always run in CI

Cross-Language Guidelines

When to Write Tests

  • Always test: boundary logic, parsing, serialization, crypto, state machines, error paths
  • Always test: bug fixes (write the failing test first)
  • Skip testing: trivial getters/setters, framework wiring, one-line delegation
  • Skip testing: third-party library behavior (that is their job)

Test Naming Conventions

LanguageConventionExample
TypeScriptdescribe("module") > it("does X when Y")it("auto-approves safe tools")
Rustsnake_case function namesfn insert_and_get()
SwiftcamelCase method names on @Suite structfunc saveLoadUpdateDeleteRoundTrip()
GoTestXxx with PascalCasefunc TestEncryptDecrypt(t *testing.T)

The Testing Pyramid

        /  E2E  \        *.e2e.test.ts, Docker scripts
       / Integration \   Server tests, handshake tests
      /    Unit Tests  \  #[test], @Test, TestXxx, it()

Ratio target: ~70% unit, ~20% integration, ~10% e2e.

What NOT to Test

  • Framework internals (Tauri IPC plumbing, axum routing wiring, SwiftUI view layout)
  • Third-party API responses (mock them instead)
  • Trivial wrappers that just delegate to another function
  • Generated code (protocol buffers, codegen output)

CI Integration (GitHub Actions)

TypeScript (Vitest):

- name: Test
  run: pnpm test
- name: Coverage
  run: pnpm test:coverage

Rust (cargo test):

- name: Test
  run: cargo test --lib
- name: Clippy
  run: cargo clippy -- -D warnings

Swift (xcodebuild):

- name: Test
  run: |
    xcodebuild test -project App.xcodeproj -scheme App \
      -destination 'platform=iOS Simulator,name=iPhone 17 Pro'

Go (go test):

- name: Test
  run: go test -race -cover ./...

Quick Reference

LanguageFrameworkRun CommandConfig FileConvention
TypeScriptVitest 4.xpnpm test / vitest runvitest.config.ts*.test.ts colocated
Rustcargo testcargo test --libCargo.toml (workspace)#[cfg(test)] mod tests
SwiftSwift Testingxcodebuild test.xcodeproj scheme*Tests.swift in Tests/
Gogo testgo test ./...None (built-in)*_test.go same pkg

Mock Strategy Per Language

LanguagePrimary Mock ApproachAvoid
TypeScriptvi.fn() + dependency injection via paramsHeavy vi.mock() of whole modules
RustTrait-based stubs (StubProvider)mockall for simple cases
Swift_test_ accessor methods + actor capturesSwizzling, runtime patching
GoInterface-based injection + channelsMonkey patching, reflect

Property-Based Testing

Use when the function has invariants you can express as a rule (commutativity, round-trip, idempotency, monotonicity). One property test replaces dozens of example cases.

LanguageLibraryQuick example
TypeScriptfast-checkfc.assert(fc.property(fc.string(), s => decode(encode(s)) === s))
Rustproptestproptest! { #[test] fn round_trip(s in any::<String>()) { prop_assert_eq!(decode(&encode(&s)), s); } }
Swiftswift-testing + manual generatorsLoop with Int.random(in:) over #expect
Gotesting/quick (stdlib) or gopterquick.Check(func(s string) bool { return decode(encode(s)) == s }, nil)

Sweet spots: parsers, codecs, serialization, math utilities, sort/merge logic. Skip for pure I/O wrappers.

Coverage Targets

LayerTarget
Pure logic, parsers90%+
Service / handler code70-80%
UI / view layer40-60%
Glue, framework wiringnot measured

Coverage is a floor, not a goal. 100% line coverage with no assertions on outputs proves nothing.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.