9.3kโ
test-patterns โ OpenClaw Skill
test-patterns is an OpenClaw Skills integration for coding workflows. 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.
Skill Snapshot
| 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. OpenClaw Skills integration. |
| owner | gitgoodordietrying |
| repository | gitgoodordietrying/test-patterns |
| language | Markdown |
| license | MIT |
| topics | |
| security | L1 |
| install | openclaw add @gitgoodordietrying/test-patterns |
| last updated | Feb 7, 2026 |
Maintainer

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
# 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
// 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
// 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
// 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
# 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
pip install pytest pytest-cov
Unit Tests
# 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
@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
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
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
# 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
// 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
# 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
// 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
}
}
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
#!/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++))
fi
}
assert_exit_code() {
local cmd="$1" expected="$2" label="$3"
eval "$cmd" >/dev/null 2>&1
assert_eq "$?" "$expected" "$label"
}
assert_contains() {
local actual="$1" substring="$2" label="$3"
if echo "$actual" | grep -q "$substring"; then
echo " PASS: $label"
((PASS++))
else
echo " FAIL: $label ('$actual' does not contain '$substring')"
((FAIL++))
fi
}
# --- Tests ---
echo "Running tests..."
# Test your scripts
output=$(./my-script.sh --help 2>&1)
assert_exit_code "./my-script.sh --help" "0" "help flag exits 0"
assert_contains "$output" "Usage" "help shows usage"
output=$(./my-script.sh --invalid 2>&1)
assert_exit_code "./my-script.sh --invalid" "1" "invalid flag exits 1"
# Test command outputs
assert_eq "$(echo 'hello' | wc -c | tr -d ' ')" "6" "echo hello is 6 bytes"
echo ""
echo "Results: $PASS passed, $FAIL failed"
[ "$FAIL" -eq 0 ] && exit 0 || exit 1
Integration Testing Patterns
API Integration Test (any language)
#!/bin/bash
# test-api.sh - Start server, run tests, tear down
SERVER_PID=""
cleanup() { [ -n "$SERVER_PID" ] && kill "$SERVER_PID" 2>/dev/null; }
trap cleanup EXIT
# Start server in background
npm start &
SERVER_PID=$!
sleep 2 # Wait for server
# Run tests against live server
BASE_URL=http://localhost:3000 npx jest --testPathPattern=integration
EXIT_CODE=$?
exit $EXIT_CODE
Database Integration Test (Python)
import pytest
import sqlite3
@pytest.fixture
def db():
"""Fresh database for each test."""
conn = sqlite3.connect(":memory:")
conn.execute("CREATE TABLE items (id INTEGER PRIMARY KEY, name TEXT, price REAL)")
yield conn
conn.close()
def test_insert_and_query(db):
db.execute("INSERT INTO items (name, price) VALUES (?, ?)", ("Widget", 9.99))
db.commit()
row = db.execute("SELECT name, price FROM items WHERE name = ?", ("Widget",)).fetchone()
assert row == ("Widget", 9.99)
def test_empty_table(db):
count = db.execute("SELECT COUNT(*) FROM items").fetchone()[0]
assert count == 0
TDD Workflow
The red-green-refactor cycle:
- Red: Write a failing test for the next piece of behavior
- Green: Write the minimum code to make it pass
- Refactor: Clean up without changing behavior (tests stay green)
# Tight feedback loop
# Jest watch mode
npx jest --watch
# Vitest watch (default)
npx vitest
# pytest watch (with pytest-watch)
pip install pytest-watch
ptw
# Go (with air or entr)
ls *.go | entr -c go test ./...
Debugging Failed Tests
Common Issues
Test passes alone, fails in suite โ shared state. Check for:
- Global variables modified between tests
- Database not cleaned up
- Mocks not restored (
afterEach/teardown)
Test fails intermittently (flaky) โ timing or ordering issue:
- Async operations without proper
await - Tests depending on execution order
- Time-dependent logic (use clock mocking)
- Network calls in unit tests (should be mocked)
Coverage shows uncovered branches โ missing edge cases:
- Error paths (what if the API returns 500?)
- Empty inputs (empty string, null, empty array)
- Boundary values (0, -1, MAX_INT)
Run Single Test
# Jest
npx jest -t "test name substring"
# pytest
pytest -k "test_divide_by_zero"
# Go
go test -run TestDivideByZero ./...
# Rust
cargo test test_divide
Tips
- Test behavior, not implementation. Tests should survive refactors.
- One assertion per concept (not necessarily one
assertper test, but one logical check). - Name tests descriptively:
test_returns_empty_list_when_no_users_existbeatstest_get_users_2. - Don't mock what you don't own โ write thin wrappers around external libraries, mock the wrapper.
- Integration tests catch bugs unit tests miss. Don't skip them.
- Use
tmp_path(pytest),t.TempDir()(Go), ortempfile(Node) for file-based tests. - Snapshot tests are great for detecting unintended changes, bad for evolving formats.
No README available.
Permissions & Security
Security level L1: Low-risk skills with minimal permissions. Review inputs and outputs before running in production.
Requirements
- OpenClaw CLI installed and configured.
- Language: Markdown
- License: MIT
- Topics:
FAQ
How do I install test-patterns?
Run openclaw add @gitgoodordietrying/test-patterns in your terminal. This installs test-patterns into your OpenClaw Skills catalog.
Does this skill run locally or in the cloud?
OpenClaw Skills execute locally by default. Review the SKILL.md and permissions before running any skill.
Where can I verify the source code?
The source repository is available at https://github.com/openclaw/skills/tree/main/skills/gitgoodordietrying/test-patterns. Review commits and README documentation before installing.
