testing-anti-patterns — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited testing-anti-patterns (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
When writing, reviewing, or debugging tests. This skill acts as a negative-space guide — it defines what NOT to do, complementing the testing-standards skill which defines what TO do. Activate whenever test suites feel brittle, when mocks dominate test code, or when tests pass but production breaks.
sleep() or arbitrary timeouts to passThe Five Named Anti-Patterns:
Anti-Pattern 1: Mock Behavior Testing
What it looks like:
// BAD: Testing that the mock returns what you told it to return
mockDb.query.mockResolvedValue([{ id: 1, name: 'Alice' }]);
const result = await getUser(1);
expect(result).toEqual({ id: 1, name: 'Alice' }); // You're testing the mock!Detection gate:
IF test assertion matches mock return value exactly
AND no transformation logic exists between mock and assertion
THEN this is Mock Behavior TestingWhy it's harmful: You're verifying your test setup, not your code. The test will pass even if getUser is return mockDb.query() with zero logic.
Fix:
// GOOD: Test the transformation/logic your code adds
mockDb.query.mockResolvedValue([{ id: 1, name: 'alice', role_id: 3 }]);
const result = await getUser(1);
// Assert on the TRANSFORMATION your code performs
expect(result.displayName).toBe('Alice'); // capitalization logic
expect(result.permissions).toContain('read'); // role mapping logicDecision tree:
Anti-Pattern 2: Test-Only Methods
What it looks like:
class PaymentService {
// This method exists ONLY so tests can inspect internal state
_getInternalQueue() { return this.queue; }
// This method exists ONLY so tests can bypass validation
_processWithoutValidation(payment) { ... }
}Detection gate:
IF method is prefixed with _ or marked @visibleForTesting
AND method is called ONLY in test files (zero production callers)
AND method exposes internal state or bypasses guards
THEN this is a Test-Only MethodWhy it's harmful: It couples tests to internals, creates maintenance burden, and the bypassed logic is now untested in the path that matters.
Fix:
Decision tree:
Anti-Pattern 3: Mocking Without Understanding
What it looks like:
// BAD: Blindly mocking everything without understanding contracts
jest.mock('./emailService');
jest.mock('./database');
jest.mock('./cache');
jest.mock('./logger');
jest.mock('./metrics');
// Test only proves: "my code calls things" — not that it calls them CORRECTLYDetection gate:
IF mock count > 3 in a single test
AND mock return values are generic/default (undefined, {}, [])
AND no assertions verify mock call arguments
THEN this is Mocking Without UnderstandingWhy it's harmful: The mocks don't reflect real behavior. When the real dependency changes its contract, these tests keep passing while production breaks.
Fix:
// GOOD: Mock with contract awareness
const mockEmail = {
send: jest.fn().mockImplementation((to, subject, body) => {
// Validate contract: email service requires these fields
if (!to.includes('@')) throw new Error('Invalid email');
if (!subject) throw new Error('Subject required');
return Promise.resolve({ messageId: 'msg-123', status: 'queued' });
})
};
// Assert on the CONTRACT, not just "was called"
expect(mockEmail.send).toHaveBeenCalledWith(
expect.stringContaining('@'),
expect.stringMatching(/Order #\d+/),
expect.objectContaining({ html: expect.any(String) })
);Decision tree:
Anti-Pattern 4: Incomplete Mocks (Happy Path Only)
What it looks like:
// BAD: Only mocking the success case
mockPaymentGateway.charge.mockResolvedValue({ success: true });
// Never testing: what if it throws? Returns { success: false }? Times out? Returns malformed data?Detection gate:
IF mock only has .mockResolvedValue / .mockReturnValue (never .mockRejectedValue)
AND the real dependency can fail (network, validation, timeout)
AND no test exercises the failure path
THEN this is Incomplete MockingWhy it's harmful: Production failures in dependency error paths are the #1 source of incidents. If you only test happy path, you're only testing the path that rarely breaks.
Fix:
// GOOD: Test all response categories
describe('PaymentService.charge', () => {
it('should process successful payment', async () => {
mockGateway.charge.mockResolvedValue({ success: true, txId: '123' });
// ...assert success handling
});
it('should handle declined card gracefully', async () => {
mockGateway.charge.mockResolvedValue({ success: false, code: 'DECLINED' });
// ...assert decline handling (user message, no retry)
});
it('should retry on transient network error', async () => {
mockGateway.charge
.mockRejectedValueOnce(new Error('ETIMEDOUT'))
.mockResolvedValue({ success: true, txId: '456' });
// ...assert retry logic
});
it('should circuit-break after 3 consecutive failures', async () => {
mockGateway.charge.mockRejectedValue(new Error('SERVICE_UNAVAILABLE'));
// ...assert circuit breaker trips
});
});Decision tree:
Anti-Pattern 5: Integration-as-Afterthought
What it looks like:
Detection gate:
IF test suite has > 50 unit tests
AND zero integration tests exist
AND code has 2+ external dependencies (DB, API, queue, cache)
THEN this is Integration-as-AfterthoughtWhy it's harmful: Unit tests prove each brick is solid. Integration tests prove the mortar holds. Without integration tests, you have a pile of solid bricks, not a wall.
Fix:
Decision tree:
Gate Function Summary — Run these checks on every test file:
GATE 1 (Iron Law 1): For each test, ask:
"If I refactored internals without changing behavior, would this test break?"
→ If YES: test is coupled to implementation. Rewrite to test behavior.
GATE 2 (Iron Law 2): For each mock assertion, ask:
"Does this verify the CONTRACT with the dependency or INTERNAL sequencing?"
→ If INTERNAL: rewrite to verify inputs/outputs at the boundary.
GATE 3 (Iron Law 3): For the module as a whole, ask:
"If all mocks were replaced with real deps, would the system work?"
→ If UNSURE: you need integration tests to find out.Remediation priority:
Before marking a task done when this skill was active:
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.