What is 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
event4u-app/agent-config
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
npx skills add https://github.com/event4u-app/agent-config --skill "src/skills/testing-anti-patterns"Quick start
Install it or open the source, trigger it with a clear task, then follow the source workflow.
npx skills add https://github.com/event4u-app/agent-config --skill "src/skills/testing-anti-patterns"Use testing-anti-patterns to help me with: [describe your task]. Before you begin, tell me what input you need, the steps you will follow, and the expected output.
No structured workflow was detected; follow the original SKILL.md below.
Continue to the workflowDirect answers
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
It is relevant to workflows involving Testing, Engineering.
SkillSignal detected this source-specific command: npx skills add https://github.com/event4u-app/agent-config --skill "src/skills/testing-anti-patterns". Inspect the repository and command before running it.
The upstream source does not declare a dedicated Agent platform.
Static analysis detected read-files signals. Review the cited source lines before installing; these signals are not a security audit.
This page combines upstream documentation with deterministic repository, quality, and static-risk signals. It is not described as a manual test or security review.
SkillSignal brief
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
Useful in these contexts
Core capabilities
Distilled from the source
About 7 min · 9 sections
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.
The mocking decision recorded as a one-line comment in the test file (// mock at : ).
The replacement test (or refactor) once an anti-pattern is identified.
If a test-only method moved out of production, the diff must show both the deletion and the test-utility addition.
1. Inspect the diff before any new mock
Anti-Pattern 1 — Asserting on mock elements
Anti-Pattern 2 — Test-only methods in production classes
Anti-Pattern 3 — Mocking without understanding
Quality breakdown
Based on traceable docs and repository signals; stars are not treated as quality.
Compare before choosing
These links are selected from shared tasks, functions, stacks, platforms, and same-name variants. Compare the source owner, documentation, permissions, and maintenance signals.
When the user wants to plan, design, or implement an A/B test or experiment, or build a growth experimentation program. Also use when the user mentions "A/B test," "split test," "experiment," "test this change," "variant copy," "multivariate test," "hypothesis," "should I test this," "which version is better," "test two versions," "statistical significance," "how long should I run this test," "growth experiments," "experiment velocity," "experiment backlog," "ICE score," "experimentation program
Use when the user says "review the design", "check the UI", or wants a comprehensive UI/UX review. Uses a 7-phase methodology covering interaction, responsiveness, accessibility, and more.
Use when writing, generating, or improving Pest tests for Laravel — clear intent, good coverage, maintainable structure, and alignment with project testing conventions.
Use when asking for a review or creating a PR — self-review first, frame the right context, test plan included — even when the user just says 'open a PR' or 'ready to merge'.
Use when authoring or rewriting a roadmap in agents/roadmaps/ — phases, goal, acceptance criteria, council notes; fires even on 'write a plan for X' / 'draft a roadmap'.
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.
Do NOT use when:
pest-testing or test-driven-development.test-case-discovery.systematic-debugging.judge-test-coverage.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.
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.
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).
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.
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.
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/.
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.
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.
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).
// mock at <seam>: <reason>).assertTrue($result)) hide mock-behavior assertions — flag any test where the assertion does not name an observable behavior.defense-in-depth often expose anti-pattern 2: if a production guard fires only in tests, the test setup is wrong, not the guard.*-mock test ids to production templates.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.pest-testing, test-driven-development, judge-test-coverage.agents/settings/contexts/skills-provenance.yml (entry: testing-anti-patterns).verify-before-complete, skill-quality, senior-engineering-discipline.ai-code-blindspots routes here for the test surface.