home / skills / lerianstudio / ring / testing-anti-patterns

testing-anti-patterns skill

/default/skills/testing-anti-patterns

This skill helps enforce test quality by detecting anti-patterns like mocking real behavior, test-only methods, and incomplete mocks in Go projects.

npx playbooks add skill lerianstudio/ring --skill testing-anti-patterns

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
4.1 KB
---
name: ring:testing-anti-patterns
description: |
  Test quality guard - prevents testing mock behavior, production pollution with
  test-only methods, and mocking without understanding dependencies.

trigger: |
  - Reviewing or modifying existing tests
  - Adding mocks to tests
  - Tempted to add test-only methods to production code
  - Tests passing but seem to test the wrong things

skip_when: |
  - Writing new tests via TDD → TDD prevents these patterns
  - Pure unit tests without mocks → check other quality concerns

related:
  complementary: [ring:test-driven-development]
---

# Testing Anti-Patterns

## Overview

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.

**Following strict TDD prevents these anti-patterns.**

## The Iron Laws

```
1. NEVER test mock behavior
2. NEVER add test-only methods to production classes
3. NEVER mock without understanding dependencies
```

## Anti-Pattern 1: Testing Mock Behavior

**BAD:** `expect(screen.getByTestId('sidebar-mock')).toBeInTheDocument()` - testing mock exists, not real behavior.

**GOOD:** `expect(screen.getByRole('navigation')).toBeInTheDocument()` - test real component or don't mock.

**Gate:** Before asserting on mock element → "Am I testing real behavior or mock existence?" If mock → delete assertion or unmock.

## Anti-Pattern 2: Test-Only Methods in Production

**BAD:** `session.destroy()` method only used in tests - pollutes production, dangerous if called.

**GOOD:** `cleanupSession(session)` in test-utils/ - keeps production clean.

**Gate:** "Is this method only used by tests?" → Put in test utilities. "Does this class own this lifecycle?" → If no, wrong class.

## Anti-Pattern 3: Mocking Without Understanding

**BAD:** Mocking `discoverAndCacheTools` breaks config write test depends on - test passes for wrong reason.

**GOOD:** Mock only the slow part (`MCPServerManager`), preserve behavior test needs.

**Gate:** Before mocking → (1) What side effects does real method have? (2) Does test depend on them? If yes → mock at lower level. **Red flags:** "Mock to be safe", "might be slow", mocking without understanding.

## Anti-Pattern 4: Incomplete Mocks

**BAD:** Partial mock missing `metadata` field - breaks when downstream code accesses `response.metadata.requestId`.

**GOOD:** Complete mock mirroring real API - ALL fields real API returns.

**Iron Rule:** Mock COMPLETE data structure, not just fields your test uses. Partial mocks fail silently.

**Gate:** Before mock → Check real API response, include ALL fields. If uncertain → include all documented fields.

## Anti-Pattern 5: Integration Tests as Afterthought

**BAD:** "Implementation complete" without tests. **FIX:** TDD cycle: write test → implement → refactor → claim complete.

## When Mocks Become Too Complex

**Warning signs:** Mock setup longer than test logic, mocking everything, mocks missing methods real components have. **Consider:** Integration tests with real components often simpler than complex mocks.

## TDD Prevents These Anti-Patterns

TDD forces: (1) Think about what you're testing, (2) Watch fail confirms real behavior not mocks, (3) See what test needs before mocking. **If testing mock behavior, you violated TDD.**

## Quick Reference

| Anti-Pattern | Fix |
|--------------|-----|
| Assert on mock elements | Test real component or unmock it |
| Test-only methods in production | Move to test utilities |
| Mock without understanding | Understand dependencies first, mock minimally |
| Incomplete mocks | Mirror real API completely |
| Tests as afterthought | TDD - tests first |
| Over-complex mocks | Consider integration tests |

## Red Flags

- Assertion checks for `*-mock` test IDs
- Methods only called in test files
- Mock setup is >50% of test
- Test fails when you remove mock
- Can't explain why mock is needed
- Mocking "just to be safe"

## The Bottom Line

**Mocks are tools to isolate, not things to test.**

If TDD reveals you're testing mock behavior, you've gone wrong.

Fix: Test real behavior or question why you're mocking at all.

Overview

This skill enforces testing quality gates to prevent common mocking anti-patterns that degrade test reliability and pollute production code. It guides engineers to test real behavior, avoid test-only production methods, and mock responsibly by understanding dependencies. The skill integrates into CI workflows to block or warn on risky test practices.

How this skill works

The skill scans test code and CI results for patterns and smells: assertions targeting mock-specific selectors, test-only methods used in production classes, excessive or incomplete mocks, and large mock setups. It applies the iron laws and decision gates to flag violations, suggest fixes (move helpers to test utilities, unmock or mock lower-level components, mirror real API responses), and optionally fail pipelines when critical rules are broken.

When to use it

  • On every pull request containing tests or changes to test helpers
  • During CI to enforce code quality gates before merging
  • When adopting TDD to keep tests focused on behavior
  • When refactoring tests to reduce flaky or brittle mocks
  • When introducing new external integrations or API clients

Best practices

  • Assert on real behavior and roles rather than mock-specific IDs or markers
  • Keep test-only logic out of production; place helpers in test utilities
  • Mock only the slow or external parts and preserve side effects tests rely on
  • Create complete mocks that mirror the real API response structure
  • Prefer small integration tests when mock setup becomes more complex than the test

Example use cases

  • Block PRs that assert on elements with '*-mock' test IDs and prompt unmocking or role-based assertions
  • Detect methods used only by tests and recommend moving them to test-utils packages
  • Warn when mock setup exceeds a set proportion of test code and suggest integration alternatives
  • Flag partial mocks missing documented response fields and require complete mock objects
  • Enforce a gate asking: 'Do you understand the mocked method's side effects?' before allowing a mock

FAQ

What if a dependency is slow or unreliable in CI?

Mock the smallest unit required (lower-level manager or network layer) while preserving side effects needed by the test, or use test doubles that simulate full API responses. If mocks become complex, prefer a focused integration test.

How do I handle cleanup or lifecycle actions used only in tests?

Move those functions into test utilities or fixtures. Avoid adding lifecycle or destroy methods to production classes solely for tests; keep production APIs minimal and safe.