# Test Async

> Async testing patterns with race condition and timing issue detection Use when this capability is needed.

- Skill: `tomevault-io/test-async` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add tomevault-io/test-async`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tomevault-io/test-async/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: tomevault-io (https://skillmd.com/u/tomevault-io)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/tomevault-io/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:**
```bash
# 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:
```bash
# 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
   ```javascript
   // BAD: Promise not awaited
   async function test() {
       doAsyncOperation(); // Forgot await!
       expect(result).toBe(expected); // Runs before async completes
   }
   ```

2. **Floating Promises** - Unhandled rejections
   ```javascript
   // BAD: No error handling
   async function test() {
       Promise.all([op1(), op2()]); // Missing await + no catch
   }
   ```

3. **Race Conditions in Assertions**
   ```javascript
   // 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**
   ```javascript
   // BAD: Timing-dependent tests
   setTimeout(() => {
       expect(callback).toHaveBeenCalled();
       done();
   }, 100); // Brittle, slow, flaky
   ```

5. **Missing done() with Callbacks**
   ```javascript
   // 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
   ```python
   # BAD: Missing pytest-asyncio marker
   async def test_async_function():
       result = await async_operation()
       assert result == expected
   ```

2. **Blocking calls in async** - Deadlock risk
   ```python
   # 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
   ```go
   // 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
   ```go
   // 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:**
```bash
# 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)
   ```javascript
   // GOOD: Condition-based waiting
   await waitFor(() => {
       expect(screen.getByText('Loaded')).toBeInTheDocument();
   }, { timeout: 5000 });
   ```

2. **Mock Timers** (Jest)
   ```javascript
   // GOOD: Control time in tests
   jest.useFakeTimers();
   asyncOperation();
   jest.runAllTimers();
   expect(result).toBe(expected);
   ```

3. **Proper Async Assertions** (Python)
   ```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**
   ```bash
   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:**
   ```javascript
   // 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:**
   ```javascript
   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)**

```bash
# 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)**

```bash
# 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)**

```bash
# 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)**

```bash
# 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)**

```bash
# 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)**

```bash
# 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)**

```bash
# 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:**

| Scenario | Unoptimized | Optimized | Savings |
|----------|-------------|-----------|---------|
| No async patterns | 3,000 | 200 | 93% |
| No critical issues | 4,000 | 700 | 82% |
| Critical issues found | 5,000 | 2,200 | 56% |
| **Average** | **3,500** | **1,400** | **60%** |

### Cache Strategy

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

**Cached Data:**
```json
{
  "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)**
```bash
# 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**
```bash
# 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)**
```bash
# 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.

---
> Converted and distributed by [TomeVault](https://tomevault.io/claim/manastalukdar) — claim your Tome and manage your conversions.
<!-- tomevault:4.0:skill_md:2026-04-13 -->

