Back to Skills
    ๐Ÿฆž

    test-patterns

    Write and run tests across languages and frameworks.

    By @gitgoodordietrying
    View on GitHub
    SKILL.md
    ---
    name: test-patterns
    description: Write and run tests across languages and frameworks. Use when setting up test suites, writing unit/integration/E2E tests, measuring coverage, mocking dependencies, or debugging test failures. Covers Node.js (Jest/Vitest), Python (pytest), Go, Rust, and Bash.
    metadata: {"clawdbot":{"emoji":"๐Ÿงช","requires":{"anyBins":["node","python3","go","cargo","bash"]},"os":["linux","darwin","win32"]}}
    ---
    
    # Test Patterns
    
    Write, run, and debug tests across languages. Covers unit tests, integration tests, E2E tests, mocking, coverage, and TDD workflows.
    
    ## When to Use
    
    - Setting up a test suite for a new project
    - Writing unit tests for functions or modules
    - Writing integration tests for APIs or database interactions
    - Setting up code coverage measurement
    - Mocking external dependencies (APIs, databases, file system)
    - Debugging flaky or failing tests
    - Implementing test-driven development (TDD)
    
    ## Node.js (Jest / Vitest)
    
    ### Setup
    
    ```bash
    # Jest
    npm install -D jest
    # Add to package.json: "scripts": { "test": "jest" }
    
    # Vitest (faster, ESM-native)
    npm install -D vitest
    # Add to package.json: "scripts": { "test": "vitest" }
    ```
    
    ### Unit Tests
    
    ```javascript
    // math.js
    export function add(a, b) { return a + b; }
    export function divide(a, b) {
      if (b === 0) throw new Error('Division by zero');
      return a / b;
    }
    
    // math.test.js
    import { add, divide } from './math.js';
    
    describe('add', () => {
      test('adds two positive numbers', () => {
        expect(add(2, 3)).toBe(5);
      });
    
      test('handles negative numbers', () => {
        expect(add(-1, 1)).toBe(0);
      });
    
      test('handles zero', () => {
        expect(add(0, 0)).toBe(0);
      });
    });
    
    describe('divide', () => {
      test('divides two numbers', () => {
        expect(divide(10, 2)).toBe(5);
      });
    
      test('throws on division by zero', () => {
        expect(() => divide(10, 0)).toThrow('Division by zero');
      });
    
      test('handles floating point', () => {
        expect(divide(1, 3)).toBeCloseTo(0.333, 3);
      });
    });
    ```
    
    ### Async Tests
    
    ```javascript
    // api.test.js
    import { fetchUser } from './api.js';
    
    test('fetches user by id', async () => {
      const user = await fetchUser('123');
      expect(user).toHaveProperty('id', '123');
      expect(user).toHaveProperty('name');
      expect(user.name).toBeTruthy();
    });
    
    test('throws on missing user', async () => {
      await expect(fetchUser('nonexistent')).rejects.toThrow('Not found');
    });
    ```
    
    ### Mocking
    
    ```javascript
    // Mock a module
    jest.mock('./database.js');
    import { getUser } from './database.js';
    import { processUser } from './service.js';
    
    test('processes user from database', async () => {
      // Setup mock return value
      getUser.mockResolvedValue({ id: '1', name: 'Alice', active: true });
    
      const result = await processUser('1');
      expect(result.processed).toBe(true);
      expect(getUser).toHaveBeenCalledWith('1');
      expect(getUser).toHaveBeenCalledTimes(1);
    });
    
    // Mock fetch
    global.fetch = jest.fn();
    
    test('calls API with correct params', async () => {
      fetch.mockResolvedValue({
        ok: true,
        json: async () => ({ data: 'test' }),
      });
    
      const result = await myApiCall('/endpoint');
      expect(fetch).toHaveBeenCalledWith('/endpoint', expect.objectContaining({
        method: 'GET',
      }));
    });
    
    // Spy on existing method (don't replace, just observe)
    const consoleSpy = jest.spyOn(console, 'log').mockImplementation();
    // ... run code ...
    expect(consoleSpy).toHaveBeenCalledWith('expected message');
    consoleSpy.mockRestore();
    ```
    
    ### Coverage
    
    ```bash
    # Jest
    npx jest --coverage
    
    # Vitest
    npx vitest --coverage
    
    # Check coverage thresholds (jest.config.js)
    # coverageThreshold: { global: { branches: 80, functions: 80, lines: 80, statements: 80 } }
    ```
    
    ## Python (pytest)
    
    ### Setup
    
    ```bash
    pip install pytest pytest-cov
    ```
    
    ### Unit Tests
    
    ```python
    # calculator.py
    def add(a, b):
        return a + b
    
    def divide(a, b):
        if b == 0:
            raise ValueError("Division by zero")
        return a / b
    
    # test_calculator.py
    import pytest
    from calculator import add, divide
    
    def test_add():
        assert add(2, 3) == 5
    
    def test_add_negative():
        assert add(-1, 1) == 0
    
    def test_divide():
        assert divide(10, 2) == 5.0
    
    def test_divide_by_zero():
        with pytest.raises(ValueError, match="Division by zero"):
            divide(10, 0)
    
    def test_divide_float():
        assert divide(1, 3) == pytest.approx(0.333, abs=0.001)
    ```
    
    ### Parametrized Tests
    
    ```python
    @pytest.mark.parametrize("a,b,expected", [
        (2, 3, 5),
        (-1, 1, 0),
        (0, 0, 0),
        (100, -50, 50),
    ])
    def test_add_cases(a, b, expected):
        assert add(a, b) == expected
    ```
    
    ### Fixtures
    
    ```python
    import pytest
    import json
    import tempfile
    import os
    
    @pytest.fixture
    def sample_users():
        """Provide test user data."""
        return [
            {"id": 1, "name": "Alice", "email": "alice@test.com"},
            {"id": 2, "name": "Bob", "email": "bob@test.com"},
        ]
    
    @pytest.fixture
    def temp_db(tmp_path):
        """Provide a temporary SQLite database."""
        import sqlite3
        db_path = tmp_path / "test.db"
        conn = sqlite3.connect(str(db_path))
        conn.execute("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT)")
        conn.commit()
        yield conn
        conn.close()
    
    def test_insert_users(temp_db, sample_users):
        for user in sample_users:
            temp_db.execute("INSERT INTO users VALUES (?, ?, ?)",
                           (user["id"], user["name"], user["email"]))
        temp_db.commit()
        count = temp_db.execute("SELECT COUNT(*) FROM users").fetchone()[0]
        assert count == 2
    
    # Fixture with cleanup
    @pytest.fixture
    def temp_config_file():
        path = tempfile.mktemp(suffix=".json")
        with open(path, "w") as f:
            json.dump({"key": "value"}, f)
        yield path
        os.unlink(path)
    ```
    
    ### Mocking
    
    ```python
    from unittest.mock import patch, MagicMock, AsyncMock
    
    # Mock a function
    @patch('mymodule.requests.get')
    def test_fetch_data(mock_get):
        mock_get.return_value.status_code = 200
        mock_get.return_value.json.return_value = {"data": "test"}
    
        result = fetch_data("https://api.example.com")
        assert result == {"data": "test"}
        mock_get.assert_called_once_with("https://api.example.com")
    
    # Mock async
    @patch('mymodule.aiohttp.ClientSession.get', new_callable=AsyncMock)
    async def test_async_fetch(mock_get):
        mock_get.return_value.__aenter__.return_value.json = AsyncMock(return_value={"ok": True})
        result = await async_fetch("/endpoint")
        assert result["ok"] is True
    
    # Context manager mock
    def test_file_reader():
        with patch("builtins.open", MagicMock(return_value=MagicMock(
            read=MagicMock(return_value='{"key": "val"}'),
            __enter__=MagicMock(return_value=MagicMock(read=MagicMock(return_value='{"key": "val"}'))),
            __exit__=MagicMock(return_value=False),
        ))):
            result = read_config("fake.json")
            assert result["key"] == "val"
    ```
    
    ### Coverage
    
    ```bash
    # Run with coverage
    pytest --cov=mypackage --cov-report=term-missing
    
    # HTML report
    pytest --cov=mypackage --cov-report=html
    # Open htmlcov/index.html
    
    # Fail if coverage below threshold
    pytest --cov=mypackage --cov-fail-under=80
    ```
    
    ## Go
    
    ### Unit Tests
    
    ```go
    // math.go
    package math
    
    import "errors"
    
    func Add(a, b int) int { return a + b }
    
    func Divide(a, b float64) (float64, error) {
        if b == 0 {
            return 0, errors.New("division by zero")
        }
        return a / b, nil
    }
    
    // math_test.go
    package math
    
    import (
        "testing"
        "math"
    )
    
    func TestAdd(t *testing.T) {
        tests := []struct {
            name     string
            a, b     int
            expected int
        }{
            {"positive", 2, 3, 5},
            {"negative", -1, 1, 0},
            {"zeros", 0, 0, 0},
        }
        for _, tt := range tests {
            t.Run(tt.name, func(t *testing.T) {
                got := Add(tt.a, tt.b)
                if got != tt.expected {
                    t.Errorf("Add(%d, %d) = %d, want %d", tt.a, tt.b, got, tt.expected)
                }
            })
        }
    }
    
    func TestDivide(t *testing.T) {
        result, err := Divide(10, 2)
        if err != nil {
            t.Fatalf("unexpected error: %v", err)
        }
        if math.Abs(result-5.0) > 0.001 {
            t.Errorf("Divide(10, 2) = %f, want 5.0", result)
        }
    }
    
    func TestDivideByZero(t *testing.T) {
        _, err := Divide(10, 0)
        if err == nil {
            t.Error("expected error for division by zero")
        }
    }
    ```
    
    ### Run Tests
    
    ```bash
    # All tests
    go test ./...
    
    # Verbose
    go test -v ./...
    
    # Specific package
    go test ./pkg/math/
    
    # With coverage
    go test -cover ./...
    go test -coverprofile=coverage.out ./...
    go tool cover -html=coverage.out
    
    # Run specific test
    go test -run TestAdd ./...
    
    # Race condition detection
    go test -race ./...
    
    # Benchmark
    go test -bench=. ./...
    ```
    
    ## Rust
    
    ### Unit Tests
    
    ```rust
    // src/math.rs
    pub fn add(a: i64, b: i64) -> i64 { a + b }
    
    pub fn divide(a: f64, b: f64) -> Result<f64, String> {
        if b == 0.0 { return Err("division by zero".into()); }
        Ok(a / b)
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_add() {
            assert_eq!(add(2, 3), 5);
            assert_eq!(add(-1, 1), 0);
        }
    
        #[test]
        fn test_divide() {
            let result = divide(10.0, 2.0).unwrap();
            assert!((result - 5.0).abs() < f64::EPSILON);
        }
    
        #[test]
        fn test_divide_by_zero() {
            assert!(divide(10.0, 0.0).is_err());
        }
    
        #[test]
        #[should_panic(expected = "overflow")]
        fn test_overflow_panics() {
            let _ = add(i64::MAX, 1); // Will panic on overflow in debug
        }
    }
    ```
    
    ```bash
    cargo test
    cargo test -- --nocapture  # Show println output
    cargo test test_add        # Run specific test
    cargo tarpaulin            # Coverage (install: cargo install cargo-tarpaulin)
    ```
    
    ## Bash Tests
    
    ### Simple Test Runner
    
    ```bash
    #!/bin/bash
    # test.sh - Minimal bash test framework
    PASS=0 FAIL=0
    
    assert_eq() {
      local actual="$1" expected="$2" label="$3"
      if [ "$actual" = "$expected" ]; then
        echo "  PASS: $label"
        ((PASS++))
      else
        echo "  FAIL: $label (got '$actual', expected '$expected')"
        ((FAIL++))
      
    
    ... (truncated)