TDD anti-patterns — writing code before tests, testing implementation details instead of behavior, using waitForTimeout as a sync strategy, chaining tests that share state, mocking the system under test instead of its dependencies.
From clarcnpx claudepluginhub marvinrichter/clarc --plugin clarcThis skill uses the workspace's default tool permissions.
Designs and optimizes AI agent action spaces, tool definitions, observation formats, error recovery, and context for higher task completion rates.
Enables AI agents to execute x402 payments with per-task budgets, spending controls, and non-custodial wallets via MCP tools. Use when agents pay for APIs, services, or other agents.
Compares coding agents like Claude Code and Aider on custom YAML-defined codebase tasks using git worktrees, measuring pass rate, cost, time, and consistency.
This skill extends tdd-workflow with common testing mistakes and how to fix them. Load tdd-workflow first.
waitForTimeout / sleep as a synchronization mechanismWrong:
// implement first, then try to write tests around it
export function calculateDiscount(price: number, tier: string): number {
if (tier === 'gold') return price * 0.8
if (tier === 'silver') return price * 0.9
return price
}
// tests written after — they describe what the code happens to do, not what it should do
test('gold tier returns 80%', () => {
expect(calculateDiscount(100, 'gold')).toBe(80)
})
Correct:
// write failing test first
test('gold tier gets 20% discount', () => {
expect(calculateDiscount(100, 'gold')).toBe(80) // RED: function does not exist yet
})
// then implement the minimum to make it pass
export function calculateDiscount(price: number, tier: string): number {
if (tier === 'gold') return price * 0.8
return price
}
Why: Code written before tests tends to be untestable by design; tests written after only verify what was already built, not what was required.
Wrong:
test('calls internal _normalize before saving', () => {
const spy = jest.spyOn(service as any, '_normalize')
service.save(user)
expect(spy).toHaveBeenCalled() // tightly coupled to private method name
})
Correct:
test('saves user with normalized email', async () => {
await service.save({ ...user, email: ' User@EXAMPLE.COM ' })
const saved = await repo.findById(user.id)
expect(saved.email).toBe('user@example.com') // tests observable outcome
})
Why: Spying on private internals makes tests brittle to safe refactors; test observable inputs and outputs instead.
setTimeout / waitForTimeout as a Synchronization StrategyWrong:
test('search results appear after typing', async ({ page }) => {
await page.fill('input[name="search"]', 'election')
await page.waitForTimeout(600) // arbitrary debounce guess
await expect(page.locator('[data-testid="result"]')).toBeVisible()
})
Correct:
test('search results appear after typing', async ({ page }) => {
await page.fill('input[name="search"]', 'election')
await expect(page.locator('[data-testid="result"]')).toBeVisible({ timeout: 5000 })
// Playwright retries the assertion until it passes or times out
})
Why: Fixed timeouts are both slow and fragile; use framework retries or explicit state-change assertions instead.
Wrong:
// test A creates the user
test('creates user', async () => {
await api.post('/users', { name: 'Alice' })
})
// test B depends on test A having run first
test('updates user', async () => {
await api.put('/users/alice', { name: 'Alice B.' })
const user = await api.get('/users/alice')
expect(user.name).toBe('Alice B.')
})
Correct:
test('updates user', async () => {
// arrange: own setup, independent of other tests
await api.post('/users', { name: 'Alice' })
await api.put('/users/alice', { name: 'Alice B.' })
const user = await api.get('/users/alice')
expect(user.name).toBe('Alice B.')
})
Why: Tests that share state fail non-deterministically when run in isolation or in a different order.
Wrong:
jest.mock('./userService') // mocks the thing we're actually testing
const { registerUser } = require('./userService')
registerUser.mockResolvedValue({ id: '1', name: 'Alice' })
test('registerUser returns a user', async () => {
const result = await registerUser('Alice', 'alice@example.com')
expect(result.name).toBe('Alice') // only tests the mock, not the real code
})
Correct:
jest.mock('./userRepository') // mock the dependency, not the SUT
import { registerUser } from './userService'
import { userRepository } from './userRepository'
;(userRepository.save as jest.Mock).mockResolvedValue({ id: '1', name: 'Alice' })
test('registerUser persists and returns the new user', async () => {
const result = await registerUser('Alice', 'alice@example.com')
expect(result.name).toBe('Alice')
expect(userRepository.save).toHaveBeenCalledWith(expect.objectContaining({ name: 'Alice' }))
})
Why: Mocking the system under test bypasses all real logic; mock dependencies at the boundary, not the subject.
Remember: Tests are not optional. They are the safety net that enables confident refactoring, rapid development, and production reliability.
tdd-workflow — Red-Green-Refactor cycle, test types, coverage targets, testing best practicestypescript-testing — TypeScript-specific testing patterns and Jest/Vitest setupe2e-testing — Playwright E2E testing patterns