liveBook · Manning

Antipattern

As explained in The Art of Unit Testing 3e

An antipattern is a commonly used approach that appears reasonable but produces undesirable consequences or problems.

An antipattern is an approach that seems sensible at first but creates trouble in practice. In automated testing, antipatterns often make tests less trustworthy, harder to read, and more expensive to maintain. The problem is not merely that a test looks untidy: test logic can contain bugs, test names can become vague, and harmless production changes can cause failures that reveal nothing useful. section 7.3.2 section 8.3.1

A useful distinction is between testing an observable outcome and testing incidental implementation details. A test should check an exit point: a returned value for a value-based test, or an affected sibling property or function for a state-based test. It should not normally care which protected helper was called internally. Otherwise, a refactoring can break the test even when the behavior users rely on is unchanged. section 8.3.1

Avoid a monster test

A test that loops through several name inputs and uses an if/else to decide each expected result combines multiple scenarios and repeats production logic. Split it into focused tests with hardcoded inputs and assertions instead. Each test can then name one behavior clearly, be recreated easily when it fails, and avoid bugs in its own control flow. section 7.3.2

Assert what matters, not every detail

A full-array equality assertion can accidentally require a particular result order and object schema. If the behavior being tested is whether each input passes, assert those result values directly; when ordering is irrelevant, locate the result for a given input rather than indexing the array. Similarly, match the meaningful part of a generated message instead of requiring its entire wording when only that part matters. section 8.3.2

Common testing antipatterns and their consequences include:

  • Putting loops, branches, or duplicated production logic in one broad test. The test becomes harder to understand, reproduce, name, and trust. section 7.3.2
  • Mocking and verifying a protected internal helper instead of the value or state exposed by the unit of work. The test becomes coupled to implementation details. section 8.3.1
  • Asserting an entire collection, exact ordering, or complete strings when those details are not the behavior under test. Small irrelevant changes then create maintenance work. section 8.3.2
  • Relying almost entirely on unit or component tests. Fast feedback remains useful, but it may not provide enough confidence that the application works as an integrated whole. section 10.2.2

The remedy is selective, not absolute. Keep unit tests, but favor small focused scenarios and value-based checks where possible. Retain complex or monster tests only as additional tests rather than replacements for simpler ones, and keep their number limited. At the strategy level, combine low-level tests with higher-level tests so fast feedback is balanced with confidence in integrated behavior. section 7.3.2 section 8.3.1 section 10.2.2

Listing 8.14 Overspecifying order and schema of the result
test("overspecify order and schema", () => {
  const pv5 = 
    new PasswordVerifier5([input => input.includes("abc")]);
 
  const results = pv5.verify(["a", "ab", "abc", "abcd"]);
 
  expect(results).toEqual([           #1
    { input: "a", result: false },    #1
    { input: "ab", result: false },   #1
    { input: "abc", result: true },   #1
    { input: "abcd", result: true },  #1
  ]);
});

Compare the full collection assertion with narrower alternatives to see how an assertion can accidentally lock down order and object structure. section 8.3.2

How The Art of Unit Testing 3e covers it

How deep it goes
Explains concrete failure modes and replacements for complex tests, internal mocks, overspecified outputs, and low-level-only strategies.
Examples
Uses name validation, password verifiers, protected-function mocks, result arrays, and generated status messages.
What it assumes
Introduces testing pitfalls through small examples without assuming prior framework knowledge.
What you can do after
Evaluate/understand fragile tests and replace irrelevant implementation or output checks with focused behavior assertions.
How it connects
Connects antipatterns to unit tests, integration tests, end-to-end tests, mocks, value-based tests, and state-based tests.

Also explained in

Compare how 5 books explain antipattern →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of The Art of Unit Testing 3e. The section links point to the parts of the book it draws on.