Every anti-pattern in this lesson has already been explained mechanically somewhere earlier in this domain — this is the field-reference version, the shape each one actually takes in a real codebase, so it's recognizable on sight.
4 min read
test("processes the payment", async () => {
await processPayment(order); // runs the code, checks NOTHING about what happened
});Covered mechanically in the coverage lesson: this test gives processPayment real coverage — the line genuinely executes — while verifying literally nothing, including nothing about whether the payment actually succeeded, charged the right amount, or threw silently. The fix: every test needs at least one assertion that would actually fail if the code under test were broken; if you can't think of one, the test isn't testing anything yet.
test("applies the discount", () => {
const calculator = { compute: jest.fn(() => 90) };
const result = applyDiscount(order, calculator);
expect(result).toBe(90); // verifies the MOCK returns 90, not that applyDiscount's logic is correct
});Covered mechanically in the over-mocking lesson: calculator.compute was told to return 90, so asserting the result is 90 proves the mock works, not that applyDiscount's real logic does. The fix: mock only genuine boundaries (network, database, real time) — call real internal logic directly and check its real output.
// Flaky — 500ms is a guess; sometimes too short (fails), always wastes time when it's too long
await new Promise((resolve) => setTimeout(resolve, 500));
expect(screen.getByText("Loaded")).toBeInTheDocument();
// Fixed — waits for the ACTUAL condition, however long it genuinely takes
expect(await screen.findByText("Loaded")).toBeInTheDocument();Covered mechanically in the async and flaky-tests lessons: a fixed sleep is a guess at how long an async operation usually takes, which means it's either too short sometimes (a flaky failure under real-world load variance) or wastefully long always (padding every test run "just in case"). The fix: wait for the actual condition to become true (findBy*, waitFor), not for an arbitrary amount of wall-clock time to pass.
// Fragile — breaks on ANY internal refactor, even ones that change nothing user-visible
expect(wrapper.instance().state.isLoading).toBe(false);
// Robust — tests what's actually observable from outside the component
expect(screen.queryByText("Loading...")).not.toBeInTheDocument();Covered mechanically in the React-testing lesson: reaching into a component's internal state ties the test to how the component happens to be built right now, not to what it actually does — a refactor that changes nothing user-visible still breaks the test for the wrong reason. The fix: assert on what a real user could actually observe (visible text, an element's presence, its accessible role) rather than internal implementation details.
Heavy reliance on slow E2E tests (or manual QA), thin or missing unit-test
base underneath — the "ice cream cone" this domain's pyramid lesson names
directly. Symptom: CI takes 40+ minutes, and a failure still doesn't say
which specific piece of logic broke.
Covered mechanically in the test-pyramid lesson: this shape inherits the worst of both worlds — slow feedback from leaning on the expensive top layer, and imprecise failures from skipping the precise, cheap bottom layer. The fix: for logic-heavy code with no real UI or infrastructure dependency, prefer a fast, precise unit test over an E2E test that happens to also exercise that logic as a side effect.
Every one of these traces back to the same handful of ideas this domain already covered in depth: an assertion that doesn't actually constrain the outcome isn't testing anything, a test that depends on guessed timing instead of a real condition is a coin flip waiting to fail, and a test tied to how code is built rather than what it does breaks for the wrong reasons. Recognizing an anti-pattern's shape on sight — "this smells like an assertion-free test," "this smells like testing the mock" — is what separates fixing a weak test suite quickly from re-deriving these mechanisms from scratch every time one shows up.
Check your understanding
A quick comprehension check — not tracked, not graded, just for you.
1. What's wrong with a test that runs the code under test but includes no assertions at all?
2. Why does asserting on internal component state (like `wrapper.instance().state.isLoading`) count as a real anti-pattern?
3. What's the actual throughline connecting all five anti-patterns in this field-reference lesson?
Testing & QA