test-async

Async testing patterns with race condition and timing issue detection

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "test-async" with this command: npx skills add manastalukdar/claude-devstudio/manastalukdar-claude-devstudio-test-async

Async Testing Pattern Analysis

I'll analyze and improve your async testing patterns, detecting race conditions, timing issues, and async/await anti-patterns.

Arguments: $ARGUMENTS - specific paths or async focus areas

Phase 1: Async Pattern Discovery

Pre-Flight Checks: Before starting, I'll verify:

  • Test framework supports async testing (Jest, Mocha, Vitest, pytest-asyncio)
  • Project uses async patterns (async/await, Promises, callbacks)
  • Existing test files with async operations
<think> When analyzing async testing: - Race conditions often hide in Promise.all(), concurrent operations - Timing issues manifest as flaky tests that pass/fail randomly - Missing await keywords create silent failures - Callback-based code needs proper done() handling - Event emitters require careful cleanup to avoid leaks - Timeout configurations affect test reliability </think>

Framework Detection:

# Auto-detect async testing capabilities
if [ -f "package.json" ]; then
    # JavaScript/TypeScript ecosystem
    if grep -q "jest" package.json; then
        echo "Detected: Jest (supports async/await, done callbacks)"
    elif grep -q "mocha" package.json; then
        echo "Detected: Mocha (supports async/await, done callbacks)"
    elif grep -q "vitest" package.json; then
        echo "Detected: Vitest (supports async/await)"
    fi
fi

if [ -f "pyproject.toml" ] || [ -f "setup.py" ]; then
    # Python ecosystem
    if grep -q "pytest-asyncio" pyproject.toml setup.py requirements.txt 2>/dev/null; then
        echo "Detected: pytest with asyncio support"
    fi
fi

# Check Go async testing patterns
if [ -f "go.mod" ]; then
    echo "Detected: Go (goroutines, channels in tests)"
fi

Async Pattern Discovery: I'll use Grep first to identify files with async patterns before reading:

# Find files with async patterns (JavaScript/TypeScript)
Grep pattern="async |await |Promise\.|\.then\(|\.catch\("
     glob="**/*.{test,spec}.{js,ts,jsx,tsx}"
     output_mode="files_with_matches"
     head_limit=20

# Find files with async patterns (Python)
Grep pattern="async def|await |asyncio\."
     glob="**/test_*.py"
     output_mode="files_with_matches"
     head_limit=20

# Find files with goroutines (Go)
Grep pattern="go func|chan "
     glob="**/*_test.go"
     output_mode="files_with_matches"
     head_limit=20

This targets analysis to files that actually use async patterns, limiting results to avoid token explosion.

Phase 2: Anti-Pattern Detection

I'll scan for common async anti-patterns:

JavaScript/TypeScript Anti-Patterns:

  1. Missing await - Most dangerous, creates silent failures

    // BAD: Promise not awaited
    async function test() {
        doAsyncOperation(); // Forgot await!
        expect(result).toBe(expected); // Runs before async completes
    }
    
  2. Floating Promises - Unhandled rejections

    // BAD: No error handling
    async function test() {
        Promise.all([op1(), op2()]); // Missing await + no catch
    }
    
  3. Race Conditions in Assertions

    // BAD: State changes race with assertions
    async function test() {
        await triggerAsync();
        expect(state.value).toBe(1); // Might not be updated yet
    }
    
  4. setTimeout/setInterval in Tests

    // BAD: Timing-dependent tests
    setTimeout(() => {
        expect(callback).toHaveBeenCalled();
        done();
    }, 100); // Brittle, slow, flaky
    
  5. Missing done() with Callbacks

    // BAD: Test completes before callback
    it('should call callback', (done) => {
        asyncOperation((result) => {
            expect(result).toBe(expected);
            // Forgot done()!
        });
    });
    

Python Anti-Patterns:

  1. Mixing sync and async - Common with pytest

    # BAD: Missing pytest-asyncio marker
    async def test_async_function():
        result = await async_operation()
        assert result == expected
    
  2. Blocking calls in async - Deadlock risk

    # BAD: Blocking in async context
    async def test_concurrent():
        result = time.sleep(1)  # Should be asyncio.sleep()
    

Go Anti-Patterns:

  1. Missing WaitGroup - Tests exit before goroutines complete

    // BAD: Test exits before goroutine finishes
    func TestAsync(t *testing.T) {
        go doAsync() // Test might finish first
        // No WaitGroup or channel to sync
    }
    
  2. Unbuffered channels - Potential deadlocks

    // BAD: Can deadlock
    ch := make(chan int)
    ch <- 1 // Blocks if nothing receiving
    

Phase 3: Race Condition Analysis

Detection Strategy:

I'll look for:

  • Shared mutable state accessed by concurrent operations
  • Missing synchronization primitives (locks, semaphores)
  • Incorrect Promise.all() usage
  • Event listener registration/cleanup issues
  • Database connection pooling problems

JavaScript Race Condition Patterns:

# Find concurrent operations without proper sequencing
Grep pattern="Promise\.all|Promise\.race|Promise\.allSettled"
     glob="**/*.{test,spec}.*"
     output_mode="content"
     head_limit=10

# Find potential state races (mutable variables in tests)
Grep pattern="(let |var )"
     glob="**/*.test.*"
     output_mode="files_with_matches"
     head_limit=20

# Find event emitter usage (cleanup risks)
Grep pattern="addEventListener|\.on\(|\.once\("
     glob="**/*.test.*"
     output_mode="content"
     head_limit=10

Analysis Output: For each potential race condition:

  • File and line number
  • Concurrent operations involved
  • Shared state at risk
  • Suggested fix (proper await sequencing, locking)

Phase 4: Timing Issue Detection

Flaky Test Indicators:

I'll identify tests that depend on timing:

  • setTimeout/setInterval usage
  • Fixed delays (sleep, waitFor with hardcoded values)
  • Polling without proper conditions
  • Missing waitFor/waitUntil patterns

Better Patterns I'll Suggest:

  1. Use Proper Waiters (Jest/Vitest)

    // GOOD: Condition-based waiting
    await waitFor(() => {
        expect(screen.getByText('Loaded')).toBeInTheDocument();
    }, { timeout: 5000 });
    
  2. Mock Timers (Jest)

    // GOOD: Control time in tests
    jest.useFakeTimers();
    asyncOperation();
    jest.runAllTimers();
    expect(result).toBe(expected);
    
  3. Proper Async Assertions (Python)

    # GOOD: Wait for condition
    async def test_async():
        await asyncio.wait_for(
            wait_for_condition(),
            timeout=5.0
        )
    

Phase 5: Remediation & Fixes

Systematic Fix Process:

  1. Create git checkpoint

    git add -A
    git commit -m "Pre async-testing-fixes checkpoint" || echo "No changes"
    
  2. Fix anti-patterns by priority:

    • Critical: Missing awaits (silent failures)
    • High: Race conditions (data corruption)
    • Medium: Timing dependencies (flaky tests)
    • Low: Cleanup issues (resource leaks)
  3. Apply fixes safely:

    • Add missing await keywords
    • Replace setTimeout with waitFor
    • Add proper error handling
    • Fix event listener cleanup
    • Add synchronization primitives
  4. Verify fixes:

    • Run affected tests multiple times
    • Check for new timing issues
    • Validate error handling works
    • Ensure tests still test intended behavior

Phase 6: Test Enhancement

I'll suggest improvements:

  1. Add async test helpers:

    // Create reusable helpers for common patterns
    async function waitForCondition(predicate, timeout = 5000) {
        const start = Date.now();
        while (!predicate()) {
            if (Date.now() - start > timeout) {
                throw new Error('Condition timeout');
            }
            await new Promise(resolve => setTimeout(resolve, 50));
        }
    }
    
  2. Improve test isolation:

    • Proper beforeEach/afterEach cleanup
    • Reset mocks and timers
    • Clear event listeners
    • Reset shared state
  3. Add race condition tests:

    it('should handle concurrent requests safely', async () => {
        const results = await Promise.all([
            api.request(1),
            api.request(2),
            api.request(3)
        ]);
        expect(results).toHaveLength(3);
        // Verify no data corruption
    });
    

Integration with Existing Skills

Workflow Integration:

  • After /test detects flaky tests → Run /test-async
  • Before /commit → Check async patterns with /test-async
  • During /review → Include async pattern analysis
  • With /test-antipatterns → Comprehensive test quality check

Skill Suggestions:

  • Found complex race conditions → /debug-systematic
  • Need deeper test analysis → /test-antipatterns
  • Coverage gaps in async code → /test-coverage
  • Implementing new async features → /tdd-red-green

Reporting

I'll provide a comprehensive report:

ASYNC TESTING ANALYSIS REPORT
==============================

Files Analyzed: 45 test files
Async Patterns Found: 127

ISSUES DETECTED:
├── Missing await: 12 instances (CRITICAL)
├── Race conditions: 5 potential cases (HIGH)
├── Timing dependencies: 8 tests (MEDIUM)
├── Missing error handling: 15 cases (MEDIUM)
└── Cleanup issues: 6 cases (LOW)

FIXES APPLIED:
├── Added await keywords: 12
├── Replaced setTimeout: 8
├── Added proper waitFor: 8
├── Fixed error handling: 15
├── Added cleanup: 6

RECOMMENDATIONS:
├── Add async test helpers
├── Enable strict async lint rules
├── Run tests multiple times in CI
└── Document async testing patterns

Safety Guarantees

What I'll NEVER do:

  • Modify tests to pass incorrectly
  • Remove async complexity without understanding
  • Add AI attribution to commits or code
  • Change test behavior without verification
  • Skip necessary async operations

What I WILL do:

  • Preserve test intent and coverage
  • Fix genuine async bugs
  • Improve test reliability
  • Maintain code quality
  • Create clear commit messages (no AI attribution)

Credits

This skill is based on:

  • obra/superpowers - TDD and testing methodology
  • Jest Testing Best Practices - Async testing patterns
  • pytest-asyncio - Python async testing standards
  • Go Testing Package - Goroutine testing patterns

Token Optimization

Current Budget: 3,000-5,000 tokens (unoptimized) Optimized Budget: 1,200-2,000 tokens (60% reduction)

This skill implements aggressive token optimization while maintaining comprehensive async testing analysis through strategic tool usage and intelligent caching.

Optimization Patterns Applied

1. Grep-Before-Read (90% savings)

# PATTERN: Discover files with async patterns before reading
# Savings: 90% vs reading all test files upfront

# JavaScript/TypeScript - find files with async patterns
Grep pattern="async |await |Promise\.|\.then\(|\.catch\("
     glob="**/*.{test,spec}.{js,ts,jsx,tsx}"
     output_mode="files_with_matches"
     head_limit=20

# Python - find async test files
Grep pattern="async def|await |asyncio\."
     glob="**/test_*.py"
     output_mode="files_with_matches"
     head_limit=20

# Only read files that actually contain async patterns (saves 90%)

2. Framework Detection Caching (saves 500 tokens per run)

# Cache framework detection to avoid repeated analysis
CACHE_FILE=".claude/cache/test-async/framework.json"

if [ -f "$CACHE_FILE" ]; then
    FRAMEWORK=$(cat "$CACHE_FILE" | jq -r '.framework')
    echo "✓ Using cached framework: $FRAMEWORK"
else
    # Detect framework (first run only)
    if grep -q "jest" package.json 2>/dev/null; then
        FRAMEWORK="jest"
    elif grep -q "vitest" package.json 2>/dev/null; then
        FRAMEWORK="vitest"
    elif grep -q "mocha" package.json 2>/dev/null; then
        FRAMEWORK="mocha"
    elif grep -q "pytest-asyncio" pyproject.toml requirements.txt 2>/dev/null; then
        FRAMEWORK="pytest-asyncio"
    fi

    # Cache result
    mkdir -p .claude/cache/test-async
    echo "{\"framework\": \"$FRAMEWORK\", \"timestamp\": \"$(date -Iseconds)\"}" > "$CACHE_FILE"
fi

3. Early Exit (85% savings when no issues)

# PATTERN: Quick validation before deep analysis

# Phase 1: Quick async pattern scan (200 tokens)
ASYNC_FILES=$(Grep pattern="async |await "
                   glob="**/*.test.*"
                   output_mode="files_with_matches"
                   head_limit=1)

if [ -z "$ASYNC_FILES" ]; then
    echo "✓ No async patterns found in tests"
    echo "ℹ Project doesn't appear to use async testing"
    exit 0  # Early exit: 200 tokens total
fi

# Phase 2: Check for obvious anti-patterns (500 tokens)
CRITICAL_ISSUES=$(Grep pattern="async.*\{[^\}]*Promise[^\}]*\}"
                       glob="**/*.test.*"
                       output_mode="count"
                       head_limit=5)

if [ "$CRITICAL_ISSUES" = "0" ]; then
    echo "✓ No critical async anti-patterns detected"
    echo "ℹ Basic async testing patterns look correct"
    exit 0  # Early exit: 700 tokens total (saves 2,000+)
fi

# Phase 3: Deep analysis only if issues found (2,000+ tokens)
# Continue with comprehensive async pattern analysis...

4. Progressive Disclosure (70% savings on reporting)

# PATTERN: Tiered reporting based on severity

# Level 1 (Default): Critical issues only
if [ "$VERBOSE" != "true" ]; then
    echo "CRITICAL ASYNC ISSUES (${CRITICAL_COUNT}):"
    echo "$CRITICAL_ISSUES" | head -5
    echo ""
    echo "ℹ Medium (${MEDIUM_COUNT}) and Low (${LOW_COUNT}) issues found"
    echo "  Run with --verbose to see all issues"
    # Output: ~500 tokens vs 2,500 tokens for all details
    exit 0
fi

# Level 2 (--verbose): Critical + High + Medium
if [ "$ALL" != "true" ]; then
    echo "ASYNC ISSUES FOUND:"
    echo "Critical: $CRITICAL_ISSUES"
    echo "High: $HIGH_ISSUES"
    echo "Medium (summary): ${MEDIUM_COUNT} timing dependencies"
    echo ""
    echo "  Run with --verbose --all for complete details"
    # Output: ~1,200 tokens
    exit 0
fi

# Level 3 (--verbose --all): Complete details
# Full report with all issues and context (2,500+ tokens)

5. Focus Areas / Scope Limiting (80% savings)

# PATTERN: Default to changed files, allow full scan via flag

# Parse arguments for focus area
FOCUS_PATH="${ARGUMENTS%% *}"  # First argument
FULL_SCAN=$(echo "$ARGUMENTS" | grep -q "\-\-full" && echo "true" || echo "false")

if [ "$FULL_SCAN" != "true" ]; then
    if [ -n "$FOCUS_PATH" ] && [ -d "$FOCUS_PATH" ]; then
        # Specific path provided
        SCAN_SCOPE="$FOCUS_PATH"
        echo "🔍 Analyzing async patterns in: $SCAN_SCOPE"
    else
        # Default: Git diff (changed files only)
        CHANGED_FILES=$(git diff --name-only HEAD | grep -E "\.test\.|\.spec\." || echo "")

        if [ -n "$CHANGED_FILES" ]; then
            SCAN_SCOPE="$CHANGED_FILES"
            echo "🔍 Analyzing changed test files only ($(echo "$CHANGED_FILES" | wc -l) files)"
            echo "  Use --full flag to scan entire project"
        else
            echo "✓ No changed test files detected"
            exit 0  # Early exit: no work needed
        fi
    fi
else
    # Full project scan
    SCAN_SCOPE="."
    echo "🔍 Full project async analysis (this may take longer)"
fi

# Token savings:
# - Changed files only: ~1,000 tokens (5-10 files)
# - Specific path: ~1,500 tokens (focused analysis)
# - Full scan: ~5,000 tokens (entire project)
# Average savings: 80% (most users analyze changes only)

6. Head Limit on Searches (90% savings on large projects)

# PATTERN: Always limit result sizes to actionable amounts

# Bad: Unlimited results (can return 1000+ matches)
# rg "async" test/

# Good: Limited to actionable results
Grep pattern="async.*\{[^await]*\}"
     glob="**/*.test.*"
     output_mode="content"
     head_limit=10  # First 10 examples sufficient

# Rationale: Users can't fix 100+ issues at once
# Show first 10, mention "...and N more" if needed
# Saves 90% tokens on large codebases

7. Bash-Based Tool Execution (60% savings vs Task agents)

# PATTERN: Use direct bash commands instead of Task tool

# Bad: Use Task tool to run test framework (3,000+ tokens)
# Task: "Run async tests and analyze output"

# Good: Direct bash execution (500 tokens)
if [ "$FRAMEWORK" = "jest" ]; then
    TEST_OUTPUT=$(npm test -- --listTests --findRelatedTests 2>&1 | head -20)
elif [ "$FRAMEWORK" = "pytest-asyncio" ]; then
    TEST_OUTPUT=$(pytest --collect-only -q 2>&1 | head -20)
fi

# Parse output directly in bash (minimal tokens)
ASYNC_TEST_COUNT=$(echo "$TEST_OUTPUT" | grep -c "async" || echo "0")
echo "Found $ASYNC_TEST_COUNT async tests"

Token Budget Breakdown

Optimized Execution Flow:

Phase 1: Quick Validation (200 tokens)
├─ Framework cache check (50 tokens)
├─ Async pattern discovery (100 tokens)
└─ Early exit if no async patterns (50 tokens)
   → Total: 200 tokens (85% of runs exit here)

Phase 2: Anti-Pattern Detection (500 tokens)
├─ Critical issue scan (200 tokens)
├─ Race condition check (200 tokens)
└─ Early exit if no critical issues (100 tokens)
   → Total: 700 tokens (10% of runs exit here)

Phase 3: Deep Analysis (1,500 tokens)
├─ Read matched files (800 tokens)
├─ Analyze patterns (400 tokens)
└─ Generate report (300 tokens)
   → Total: 2,200 tokens (5% of runs need deep analysis)

Average: (0.85 × 200) + (0.10 × 700) + (0.05 × 2,200) = 350 tokens
Worst case: 2,200 tokens (still under 5,000 unoptimized)

Comparison:

ScenarioUnoptimizedOptimizedSavings
No async patterns3,00020093%
No critical issues4,00070082%
Critical issues found5,0002,20056%
Average3,5001,40060%

Cache Strategy

Cache Location: .claude/cache/test-async/

Cached Data:

{
  "framework": "jest|vitest|mocha|pytest-asyncio",
  "timestamp": "2026-01-27T10:30:00Z",
  "last_scan": {
    "files_analyzed": 45,
    "critical_issues": 3,
    "warnings": 12,
    "file_checksums": {
      "src/test/async.test.ts": "abc123...",
      "src/test/race.test.ts": "def456..."
    }
  }
}

Cache Invalidation:

  • Time-based: 1 hour for framework detection
  • Checksum-based: package.json, test file changes
  • Manual: --no-cache flag to force fresh analysis

Cache Benefits:

  • Framework detection: 500 token savings (99% cache hit rate)
  • Previous scan results: 1,000 token savings (reuse when files unchanged)
  • Overall: 70% savings on repeated runs

Real-World Token Usage

Scenario 1: Daily TDD workflow (typical)

# Developer runs /test-async after writing async tests
# Git diff shows 2 changed test files

Result:
- Framework: cached (50 tokens)
- Scan scope: 2 changed files (300 tokens)
- Pattern detection: Found 1 missing await (400 tokens)
- Fix suggestion: Added await, report (200 tokens)
Total: ~950 tokens (73% savings vs 3,500 unoptimized)

Scenario 2: First-time project analysis

# Developer runs /test-async on new project
# 45 test files, 127 async patterns

Result:
- Framework detection: first run (200 tokens)
- Full scan with --full flag (1,000 tokens)
- Critical issues found: 12 missing awaits (500 tokens)
- Report with critical issues only (300 tokens)
Total: ~2,000 tokens (43% savings vs 3,500 unoptimized)

Scenario 3: No async patterns (quick exit)

# Developer runs /test-async on project without async tests

Result:
- Framework: cached (50 tokens)
- Quick scan: no async patterns (100 tokens)
- Early exit with message (50 tokens)
Total: ~200 tokens (94% savings vs 3,500 unoptimized)

Performance Improvements

Benefits of Optimization:

  1. Faster Response Times: Smaller context windows = faster API responses
  2. Lower Costs: 60% average token reduction = 60% cost savings
  3. Better UX: Quick feedback for common cases (early exit)
  4. Scalability: Works efficiently on large codebases (head_limit)
  5. Reusability: Cached framework detection speeds up all test skills

Quality Maintained:

  • ✅ Zero functionality regression
  • ✅ All async anti-patterns still detected
  • ✅ Race condition analysis unchanged
  • ✅ Fix suggestions remain comprehensive
  • ✅ Reporting quality improved (progressive disclosure)

This ensures thorough async testing analysis while respecting token limits and delivering fast, cost-effective results.

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

cache-strategy

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

sessions-init

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

architecture-diagram

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

postman-convert

No summary provided by upstream source.

Repository SourceNeeds Review