Source profileQuality 100/100

event4u-app/agent-config/src/skills/testing-anti-patterns/SKILL.md

testing-anti-patterns

Use BEFORE writing/changing tests, adding mocks, or test-only methods on production classes — vs mocking-the-mock, production pollution, partial mocks, and overfit/tautological assertions

Source repository stars
7
Declared platforms
0
Static risk flags
1
Last source update
2026-07-28
Source checked
2026-07-28

Decision brief

What it does—and where it fits

Tests must verify real behavior, not mock behavior. Mocks isolate; they are not the thing under test. This skill is the prevention layer; judge-test-coverage catches what slips through afterwards.

Best for

  • About to write a new test that mocks a collaborator.
  • Tempted to add a method to a production class purely for test cleanup.
  • Mock setup is becoming longer than the test logic itself.

Not for

  • 1. Inspect the diff before any new mock
  • Anti-Pattern 1 — Asserting on mock elements

Compatibility matrix

Platform support, with evidence labels

PlatformStatusEvidenceWhat to check
CodexNot declaredNo explicit evidencePortability before use
Claude CodeNot declaredNo explicit evidencePortability before use
CursorNot declaredNo explicit evidencePortability before use
Gemini CLINot declaredNo explicit evidencePortability before use
Open the compatibility checker

Installation

Inspect first. Install second.

The source command is displayed only when detected. A safe inspection prompt is always available so your agent can explain every action before execution.

Source-detected install commandSource
npx skills add https://github.com/event4u-app/agent-config --skill "src/skills/testing-anti-patterns"
Safe inspection promptEditorial

Inspect the Agent Skill "testing-anti-patterns" from https://github.com/event4u-app/agent-config/blob/0adf49a8ae84b0ff6e2de8759eea43257e020eff/src/skills/testing-anti-patterns/SKILL.md at commit 0adf49a8ae84b0ff6e2de8759eea43257e020eff. List every install step, command, network request, credential, file read/write, external action, and rollback step. Explain whether it fits my task. Do not install or execute anything until I approve.

Workflow

What the source asks the agent to do

  1. 01

    Procedure: Run the gate before each anti-pattern

    Before writing or extending a test, inspect the code under test and identify which collaborators are real, which are mocked, and which produce side effects the assertion depends on. Open the file, read the dependency chain, and write the chain down. Do not start mocking until th…

    Before writing or extending a test, inspect the code under test and identify which collaborators are real, which are mocked, and which produce side effects the assertion depends on. Open the file, read the dependency ch…Symptom: expect(screen.getByTestId('sidebar-mock')).toBeInTheDocument() or $this-assertSee('mock-sidebar'). Test passes when the mock is present, fails when it is not — proves nothing about the component.Symptom: Session::destroy() only ever called from tearDown. Production class polluted with code dangerous in production.
  2. 02

    When to use

    About to write a new test that mocks a collaborator.

    About to write a new test that mocks a collaborator.Tempted to add a method to a production class purely for test cleanup.Mock setup is becoming longer than the test logic itself.
  3. 03

    The Iron Laws

    Review the “The Iron Laws” section in the pinned source before continuing.

    Review and apply the “The Iron Laws” source section.
  4. 04

    1. Inspect the diff before any new mock

    Before writing or extending a test, inspect the code under test and identify which collaborators are real, which are mocked, and which produce side effects the assertion depends on. Open the file, read the dependency chain, and write the chain down. Do not start mocking until th…

    Before writing or extending a test, inspect the code under test and identify which collaborators are real, which are mocked, and which produce side effects the assertion depends on. Open the file, read the dependency ch…
  5. 05

    Anti-Pattern 1 — Asserting on mock elements

    Symptom: expect(screen.getByTestId('sidebar-mock')).toBeInTheDocument() or $this-assertSee('mock-sidebar'). Test passes when the mock is present, fails when it is not — proves nothing about the component.

    Symptom: expect(screen.getByTestId('sidebar-mock')).toBeInTheDocument() or $this-assertSee('mock-sidebar'). Test passes when the mock is present, fails when it is not — proves nothing about the component.

Permission review

Static risk signals and limitations

Reads files

low · line 43

The documentation asks the agent to read local files, directories, or repositories.

Before writing or extending a test, **inspect** the code under test and identify which collaborators are real, which are mocked, and which produce side effects the assertion depends on. Open the file, read the dependency chain, and write th

Evidence record

Why each signal appears

EvidenceSourceComputedTestedEditorial
SignalValueEvidence typeMeaning
Quality score100/100ComputedDocumentation, specificity, maintenance, and trust rules
Repository stars7SourceRepository attention, not individual Skill quality
Compatibility0 platformsSourceDeclared in the catalog source record
Usage guideautomated source guideEditorialGenerated or reviewed according to the visible evidence level

Pinned source

Provenance and original SKILL.md

Repository
event4u-app/agent-config
Skill path
src/skills/testing-anti-patterns/SKILL.md
Commit
0adf49a8ae84b0ff6e2de8759eea43257e020eff
License
MIT
Collected
2026-07-28
Default branch
main
View the original SKILL.md

testing-anti-patterns

Tests must verify real behavior, not mock behavior. Mocks isolate; they are not the thing under test. This skill is the prevention layer; judge-test-coverage catches what slips through afterwards.

For the process / rationalization failure modes that fire before a test is written (the urges to skip TDD, keep code "as reference", patch without a regression test), see the sibling reference table in process-anti-patterns.md. Both layers are required — a correctly-mocked test that was written after the code is still test-after-the-fact.

When to use

  • About to write a new test that mocks a collaborator.
  • Tempted to add a method to a production class purely for test cleanup.
  • Mock setup is becoming longer than the test logic itself.
  • A test passes but you cannot explain what real behavior it verified.
  • Code review of a diff that adds mocks — run the gates below before approving.

Do NOT use when:

The Iron Laws

1. NEVER test mock behavior — assert on real component behavior.
2. NEVER add test-only methods to production classes — put them in test utilities.
3. NEVER mock without understanding the dependency chain — observe first, mock minimally.
4. NEVER ship partial mocks — mirror the real response shape completely.
5. NEVER treat tests as an afterthought — write the failing test first.
6. NEVER overfit an assertion — assert the general rule, derive expected values from inputs/seeded data, cover boundary + error cases.
7. NEVER game the green — a failing test is a finding, not an obstacle; fix the code, never skip/weaken/delete the test or rewrite `expected` to the buggy output.

Procedure: Run the gate before each anti-pattern

1. Inspect the diff before any new mock

Before writing or extending a test, inspect the code under test and identify which collaborators are real, which are mocked, and which produce side effects the assertion depends on. Open the file, read the dependency chain, and write the chain down. Do not start mocking until the chain is on paper.

Anti-Pattern 1 — Asserting on mock elements

Symptom: expect(screen.getByTestId('sidebar-mock')).toBeInTheDocument() or $this->assertSee('mock-sidebar'). Test passes when the mock is present, fails when it is not — proves nothing about the component.

Gate:

BEFORE asserting on any mocked element / id / class:
  Ask: "Am I asserting that the mock exists, or that the component behaves correctly?"

  IF asserting that the mock exists:
    STOP — delete the assertion or unmock the dependency.
    Replace with a behavior assertion (role, output, side effect).

Anti-Pattern 2 — Test-only methods in production classes

Symptom: Session::destroy() only ever called from tearDown. Production class polluted with code dangerous in production.

Gate:

BEFORE adding any method to a production class:
  Ask: "Is this only used by tests?"
  IF yes:
    STOP — move it to a test utility / trait / helper.
  Ask: "Does this class own this resource's lifecycle?"
  IF no:
    STOP — wrong class for this method.

Replacement: a tests/Support/cleanupSession.php helper or a trait used only by test classes.

Anti-Pattern 3 — Mocking without understanding

Symptom: A mocked method had a side effect the test depended on (e.g. wrote config). Mock kills the side effect; the test passes for the wrong reason or fails mysteriously.

Gate:

BEFORE mocking any method:
  STOP — do not mock yet.
  1. List the side effects the real method produces.
  2. List which of those side effects the test actually depends on.
  3. If the test depends on any of them, mock at a *lower* level (the slow / external bit), preserving the necessary behavior.
  IF unsure:
    Run the test with the real implementation FIRST. Observe what fails.
    THEN mock minimally, just below the failing seam.

  Red flags:
    - "I'll mock this just to be safe."
    - "This might be slow, better mock it."
    - You cannot draw the dependency chain.

Anti-Pattern 4 — Partial / incomplete mocks

Symptom: Mock returns only the fields the immediate test reads. Downstream code accesses an absent field and the test passes; integration breaks.

Iron Rule: mock the complete response shape that the real API returns, not just the fields your assertion uses. If you cannot enumerate the shape, you should not mock.

BEFORE creating a mock response object:
  1. Examine the real response (docs, recorded fixture, type definition).
  2. Include EVERY documented field — even ones the test does not read.
  3. If the shape is unknown, capture a real response into `tests/fixtures/` instead of inventing one.

Concrete capture tools for recording the real shape: curl -s <url> | jq '.' against a staging endpoint, Postman's "Save Response", Laravel's Http::fake() in record mode, or a Playwright network-trace export. Filter the captured payload with jq / grep to keep only the fields your fixture documents — do not dump unredacted secrets into tests/fixtures/.

Anti-Pattern 5 — Tests as an afterthought

Symptom: "Implementation complete, ready for testing." Implementation went in without tests. TDD was skipped, anti-patterns 1–4 are now likely.

Gate: a feature is not complete until a failing-then-passing test cycle ran for it. Route to test-driven-development.

Anti-Pattern 6 — Overfit / tautological assertions & unrealistic test data

Symptom: the test passes only for one crafted input and proves nothing about the general rule — a hardcoded expected value the code will always emit, a narrow regex pinned to a fixed date/id, or an assertion that restates the seed. AI-written tests reach the same coverage as human tests but ~4× worse fault detection (the weak-oracle problem): they execute the path and then assert something too weak to catch a bug. Same shape as mocking-the-mock — asserting on your own construction rather than the system's behavior.

Gate:

BEFORE writing an assertion:
  Ask: "Would this still pass if the input changed, and FAIL if the RULE broke?"
  IF it only passes for one crafted value, or restates a hardcoded literal:
    STOP — derive the expected from the input, and add cases that vary it.

Test-data realism — seeders/factories emit random data; the test derives its expectation from that data, never from a hardcoded literal:

// ❌ WRONG — hardcoded expectation, single happy case
const res = await api.get('/users/1')
expect(res.body).toEqual({ id: 1, name: 'Alice', email: 'alice@example.com' })
expect(log).toMatch(/User 1 logged in at 2026-01-01/)   // pins incidental values

// ✅ RIGHT — derive from seeded random data + cover more than the happy path
const user = await seedUser()                     // faker-random name/email
const res = await api.get(`/users/${user.id}`)
expect(res.body.id).toBe(user.id)                 // derived, not hardcoded
expect(res.body.name).toBe(user.name)
expect(res.body).not.toHaveProperty('passwordHash')            // security property
expect((await api.get('/users/999999')).status).toBe(404)      // boundary: missing
expect((await api.get('/users/abc')).status).toBe(400)         // error: invalid input
expect([403, 404]).toContain((await api.get(`/users/${otherTenantUser.id}`)).status) // abuse: tenant isolation (404 hides existence)

Deriving from seeded data is necessary but not sufficient — a test that only reads back the exact fields it seeded is still overfit (a mutation in the code survives it). A real test also exercises boundary (empty / null / max / Unicode), error (missing / invalid), and, on security-sensitive paths, an abuse case (IDOR, injection string, XSS payload). Coverage of cases, not just lines.

Anti-Pattern 7 — Gaming the green (test-integrity)

Symptom: a test fails, and the "fix" edits the test instead of the code — .skip / it.skip / xfail / @Disabled on the failing case, deleting the failing assertion, loosening toBe(x) to toBeTruthy(), or rewriting the expected value to whatever the (possibly buggy) code now emits. The suite goes green while the behavior stays broken — the worst outcome, because green now hides the bug. This is the automation-bias trap: under pressure to finish, the agent optimizes the signal (green) instead of the target (correct behavior).

Gate:

BEFORE editing a test that is currently failing:
  Ask: "Is the TEST wrong, or is the CODE wrong?"
  IF the code is wrong:
    STOP — fix the code. Editing the test to match buggy output ships the bug green.
  Editing a test is legitimate ONLY when it encodes a genuinely stale/incorrect
  expectation (a real, intended spec change) — and then the reason is stated in
  the diff, never a silent skip/xfail/delete.

Backstop grep — skip/xfail added to chase green (a hit in a diff that also "fixes" a failure is the smell):

rg -n '\b(it|test|describe)\.skip\b|\.only\b|xit\(|xdescribe\(|@pytest\.mark\.(skip|xfail)|@Disabled|->markTestSkipped\(' .

This is the test-surface instance of the autonomous-execution N=3 / allowlist-growth antipattern (bulk skip/xfail to force green counts as the tool being wrong, not the content) and the verify-before-complete floor (green must be earned, not manufactured).

Output format

  1. The mocking decision recorded as a one-line comment in the test file (// mock at <seam>: <reason>).
  2. The replacement test (or refactor) once an anti-pattern is identified.
  3. If a test-only method moved out of production, the diff must show both the deletion and the test-utility addition.

Gotcha

  • Vague-test asserts (assertTrue($result)) hide mock-behavior assertions — flag any test where the assertion does not name an observable behavior.
  • A "complete" mock that mirrors a v1 API silently rots when v2 ships — link mock fixtures to a real recorded response and re-record on schema changes.
  • Layer 3 environment guards from defense-in-depth often expose anti-pattern 2: if a production guard fires only in tests, the test setup is wrong, not the guard.
  • Long mock setups (> 50% of the test) are a signal that integration tests would be simpler — consider it before piling on more mocks.
  • Diagnose, do not brute-force. If a test fails after a mock change, never guess at another mock tweak — drop a debugger / Xdebug breakpoint at the seam, observe the real call shape, then mock minimally. Two retries without a root-cause hypothesis = STOP and rethink.
  • An assertion that derives from seeded data but only checks the happy path is still overfit — a code mutation survives it. Require ≥1 boundary/error/abuse case per behavior, not just line coverage.

Do NOT

  • Do NOT add *-mock test ids to production templates.
  • Do NOT extend a production class to expose internals "just for testing".
  • Do NOT mock a method whose side effects the test depends on without reading the implementation.
  • Do NOT invent mock data shapes from memory — record from the real source.
  • Do NOT mark a story complete until at least one test was watched failing first.
  • Do NOT hardcode an expected value the code will emit, or pin a regex to an incidental fixed date/id — derive from inputs and seeded data, and vary the input across cases.
  • Do NOT skip / xfail / delete a failing test, or rewrite its expected to the code's current output, to get a green run — fix the code, or state a real spec change in the diff.

Auto-trigger keywords

  • testing anti-patterns
  • mock behavior
  • test-only method
  • partial mock
  • mock without understanding
  • overfit assertion
  • tautological test
  • hardcoded expected value
  • gaming the green
  • skip failing test
  • test integrity

Provenance

  • Adopted from: an external reference (internal provenance, redacted).
  • Cross-linked: pest-testing, test-driven-development, judge-test-coverage.
  • Provenance registry: agents/settings/contexts/skills-provenance.yml (entry: testing-anti-patterns).
  • Iron-Law floor: verify-before-complete, skill-quality, senior-engineering-discipline.
  • Cross-cutting anchor: ai-code-blindspots routes here for the test surface.

Alternatives

Compare before choosing