Use when writing or changing tests, adding mocks, or tempted to add test-only methods to production code - prevents testing mock behavior, production pollution with test-only methods, and mocking without understanding dependencies, with quantitative anti-pattern detection scoring
Detects testing anti-patterns like testing mock behavior, adding test-only methods to production code, and incomplete mocking. Automatically scores violations (0.00-1.00) and triggers when writing or modifying tests, adding mocks, or tempted to add test-only methods to production code.
/plugin marketplace add krzemienski/shannon-framework/plugin install shannon@shannon-frameworkThis skill inherits all available tools. When active, it can use any tool Claude has access to.
Tests must verify real behavior, not mock behavior. Mocks are a means to isolate, not the thing being tested.
Core principle: Test what the code does, not what the mocks do.
Shannon enhancement: Automatically detect and score anti-patterns quantitatively.
Following strict TDD prevents these anti-patterns.
1. NEVER test mock behavior
2. NEVER add test-only methods to production classes
3. NEVER mock without understanding dependencies
4. NEVER use partial/incomplete mocks
5. SHANNON: Track anti-pattern violations quantitatively
Severity: 0.95/1.00 (CRITICAL)
The violation:
// ❌ BAD: Testing that the mock exists
test('renders sidebar', () => {
render(<Page />);
expect(screen.getByTestId('sidebar-mock')).toBeInTheDocument();
});
Why this is wrong:
Shannon detection:
# Automatically detect mock behavior testing
def detect_mock_testing(test_code: str) -> float:
"""Returns anti-pattern score 0.00-1.00"""
score = 0.0
# Check for mock-specific test IDs
if re.search(r'getByTestId.*-mock', test_code):
score += 0.50
# Check for assertions on mock existence only
if 'expect(.*mock.*).toBe' in test_code and 'real assertion' not in test_code:
score += 0.45
return min(score, 1.0)
# Track in Serena
anti_pattern_detected = {
"type": "testing_mock_behavior",
"severity": 0.95,
"test_file": test_file,
"line": line_number,
"recommendation": "Test real component behavior, not mock existence"
}
The fix:
// ✅ GOOD: Test real component or don't mock it
test('renders sidebar', () => {
render(<Page />); // Don't mock sidebar
expect(screen.getByRole('navigation')).toBeInTheDocument();
});
Severity: 0.85/1.00 (HIGH)
The violation:
// ❌ BAD: destroy() only used in tests
class Session {
async destroy() { // Looks like production API!
await this._workspaceManager?.destroyWorkspace(this.id);
}
}
Shannon detection:
# Detect test-only methods in production classes
def detect_test_only_methods(production_code: str, test_code: str) -> dict:
"""Scans for methods only called in tests"""
production_methods = extract_public_methods(production_code)
test_usage = extract_method_calls(test_code)
production_usage = extract_method_calls(production_code)
test_only = []
for method in production_methods:
if method in test_usage and method not in production_usage:
test_only.append({
"method": method,
"severity": 0.85,
"recommendation": "Move to test utilities"
})
return test_only
The fix:
// ✅ GOOD: Test utilities handle test cleanup
export async function cleanupSession(session: Session) {
const workspace = session.getWorkspaceInfo();
if (workspace) {
await workspaceManager.destroyWorkspace(workspace.id);
}
}
Severity: 0.75/1.00 (HIGH)
The violation:
// ❌ BAD: Mock breaks test logic
test('detects duplicate server', () => {
vi.mock('ToolCatalog', () => ({
discoverAndCacheTools: vi.fn().mockResolvedValue(undefined)
}));
await addServer(config);
await addServer(config); // Should throw - but won't!
});
Shannon detection:
# Detect mocking that breaks test dependencies
def detect_over_mocking(test_code: str) -> dict:
"""Identifies mocks that may break test logic"""
mocked_methods = extract_mocked_methods(test_code)
test_expectations = extract_expectations(test_code)
suspicious_mocks = []
for mock in mocked_methods:
# Check if mock returns undefined/null for methods that have side effects
if 'mockResolvedValue(undefined)' in mock['implementation']:
suspicious_mocks.append({
"mock": mock['name'],
"severity": 0.75,
"reason": "Mocking method to undefined may break test dependencies"
})
return suspicious_mocks
The fix:
// ✅ GOOD: Mock at correct level
test('detects duplicate server', () => {
vi.mock('MCPServerManager'); // Just mock slow server startup
await addServer(config); // Config written
await addServer(config); // Duplicate detected ✓
});
Severity: 0.80/1.00 (HIGH)
The violation:
// ❌ BAD: Partial mock
const mockResponse = {
status: 'success',
data: { userId: '123', name: 'Alice' }
// Missing: metadata that downstream code uses
};
Shannon detection:
# Detect incomplete mocks by comparing to real API schema
def detect_incomplete_mocks(mock_data: dict, real_schema: dict) -> dict:
"""Compares mock to real API schema"""
missing_fields = []
mock_keys = set(mock_data.keys())
schema_keys = set(real_schema.keys())
missing = schema_keys - mock_keys
completeness = len(mock_keys) / len(schema_keys) if schema_keys else 1.0
return {
"severity": 1.0 - completeness, # More missing = higher severity
"missing_fields": list(missing),
"completeness": completeness,
"recommendation": f"Add missing fields: {', '.join(missing)}"
}
The fix:
// ✅ GOOD: Mirror real API completeness
const mockResponse = {
status: 'success',
data: { userId: '123', name: 'Alice' },
metadata: { requestId: 'req-789', timestamp: 1234567890 }
};
Pre-commit hook integration:
#!/bin/bash
# hooks/pre-commit-test-quality.sh
# Scan all test files for anti-patterns
TEST_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep '\.test\.')
for test_file in $TEST_FILES; do
# Run Shannon anti-pattern detection
ANTI_PATTERNS=$(shannon_detect_anti_patterns "$test_file")
if [ -n "$ANTI_PATTERNS" ]; then
echo "⚠️ ANTI-PATTERNS DETECTED in $test_file:"
echo "$ANTI_PATTERNS" | jq -r '.[] | " - \(.type): \(.severity) severity - \(.recommendation)"'
HIGH_SEVERITY=$(echo "$ANTI_PATTERNS" | jq '[.[] | select(.severity > 0.70)] | length')
if [ "$HIGH_SEVERITY" -gt 0 ]; then
echo ""
echo "❌ BLOCKING COMMIT: $HIGH_SEVERITY high-severity anti-patterns detected"
echo "See: /shannon:skill testing-anti-patterns"
exit 1
fi
fi
done
Continuous monitoring:
# Track anti-pattern metrics over time
anti_pattern_metrics = {
"project": project_name,
"scan_date": ISO_timestamp,
"total_tests": 234,
"anti_patterns_found": 12,
"by_severity": {
"critical": 2, # 0.90-1.00
"high": 5, # 0.70-0.89
"medium": 3, # 0.40-0.69
"low": 2 # 0.00-0.39
},
"by_type": {
"testing_mock_behavior": 4,
"test_only_methods": 3,
"over_mocking": 2,
"incomplete_mocks": 3
},
"avg_severity": 0.63,
"trend": "improving" # vs last scan
}
serena.write_memory(f"test_quality/anti_patterns/{scan_id}", anti_pattern_metrics)
Formula:
def calculate_test_quality_score(test_suite: dict) -> float:
"""
Comprehensive test quality scoring
Returns: 0.00 (poor) to 1.00 (excellent)
"""
# Factors
anti_pattern_penalty = sum([ap["severity"] for ap in test_suite["anti_patterns"]])
mock_ratio = test_suite["mocked_tests"] / test_suite["total_tests"]
integration_ratio = test_suite["integration_tests"] / test_suite["total_tests"]
flakiness = test_suite["avg_flakiness_score"]
# Score calculation
base_score = 1.0
base_score -= (anti_pattern_penalty / test_suite["total_tests"]) # Penalize anti-patterns
base_score -= (mock_ratio * 0.2) # Penalize excessive mocking
base_score += (integration_ratio * 0.1) # Reward integration tests
base_score -= (flakiness * 0.3) # Penalize flakiness
return max(0.0, min(1.0, base_score))
# Example output
test_quality = {
"overall_score": 0.78, # GOOD (0.70-0.85)
"grade": "B+",
"total_tests": 234,
"anti_patterns": 12,
"mock_ratio": 0.35,
"integration_ratio": 0.25,
"avg_flakiness": 0.04,
"recommendations": [
"Reduce mock usage from 35% to <25%",
"Fix 2 critical anti-patterns in auth tests",
"Add 20 more integration tests for 30% coverage"
]
}
Why TDD helps:
If you're testing mock behavior, you violated TDD.
| Anti-Pattern | Severity | Fix |
|---|---|---|
| Testing mock behavior | 0.95 | Test real component or unmock it |
| Test-only methods | 0.85 | Move to test utilities |
| Mocking without understanding | 0.75 | Understand dependencies, mock minimally |
| Incomplete mocks | 0.80 | Mirror real API completely |
| Tests as afterthought | 0.90 | TDD - tests first |
This skill works with:
Shannon integration:
Mocks are tools to isolate, not things to test.
Shannon's quantitative detection turns test quality from subjective to measurable.
Scan automatically. Score objectively. Fix systematically.
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.