ConstructiCat Logo
CodeBust.
Browse section ▾

Verbose Test.

A test that uses far more code than it needs to state its scenario, burying the one cause-and-effect it verifies under setup boilerplate, irrelevant data, and field-by-field assertions.

##Signs and Symptoms

You can't tell what a test proves at a glance, even though nothing tricky is going on — the intent is drowned in volume. Common tells:

  • The test method is long (the Test Smells catalog uses a rough threshold of >30 lines), or scrolls past one screen.
  • A large arrange block builds objects field-by-field, much of it irrelevant to the behavior under test.
  • Lots of hard-coded literals with no obvious link between inputs and the asserted outputs.
  • The result is checked with a pile of single-field assertions instead of one meaningful comparison.
  • Reading the assertions, you can't easily map which input caused which expected value.
test('places an order for a returning customer', () => {
  const address = new Address();
  address.street = '123 Main St';
  address.city = 'Springfield';
  address.zip = '00000';                          // irrelevant to this test
  address.country = 'US';

  const customer = new Customer();
  customer.id = 42;
  customer.firstName = 'Ada';
  customer.lastName = 'Lovelace';
  customer.email = 'ada@example.com';
  customer.address = address;
  customer.createdAt = new Date('2020-01-01');     // noise

  const product = new Product();
  product.sku = 'SKU-1';
  product.name = 'Widget';
  product.price = 9.99;

  const cart = new Cart();
  cart.add(product, 2);

  const order = orderService.placeOrder(customer, cart);

  expect(order.status).toBe('CONFIRMED');          // the only lines that matter
  expect(order.lineItems.length).toBe(1);
  expect(order.lineItems[0].sku).toBe('SKU-1');
  expect(order.lineItems[0].quantity).toBe(2);
  expect(order.total).toBe(19.98);
  expect(order.customerId).toBe(42);
});

Roughly 25 lines of construction guard a two-line assertion about totals and status.

##Reasons for the Problem

Why it happens

  • Inline construction with required fields. Building real objects through constructors/setters forces you to supply data the test doesn't care about, just to make the code compile and run. Meszaros notes the level of detail needed to make a test executable can make it "so verbose as to be difficult to understand."
  • Copy-paste growth. A test starts small, then each new scenario is created by copying the previous one and tweaking a value, so noise accumulates.
  • No shared construction helpers. Without Creation Methods, builders, or fixtures, every test re-states the full object graph.
  • Hard-coded data and field-by-field verification. Writing literals everywhere and asserting on each property feels "thorough," but multiplies line count.

Why it hurts

  • Readability. Verbose Test is a flavor of Obscure Test — the catalog even lists "Obscure Test" as its alias. The reader can't see the cause-and-effect between fixture and outcome, which is the whole point of an example-based test.
  • Maintainability. A 30+ line test tends to carry "several responsibilities," so a small production change ripples into many lines across many tests. Bulk inline setup also breeds duplication.
  • False confidence. Long, noisy tests hide bugs in plain sight: an incorrect literal or a misplaced assertion is easy to miss, and verbosity makes reviewers skim. The volume looks rigorous without proving more.
  • Reliability. The more irrelevant state a test pins down, the more likely it breaks for reasons unrelated to its intent (over-specification), producing brittle, low-signal failures.

##Treatment

Push the noise out of the test body so only the variables that drive the behavior remain visible. Concrete moves (all from xUnit Test Patterns):

  1. Extract Creation Methods / use a builder. Replace inline object construction with an evocatively named helper that supplies sensible defaults; pass in only the values the test actually cares about (Parameterized Creation Method). This kills both boilerplate and Irrelevant Information.
  2. Default the irrelevant data inside the helper, signalling "these values don't affect the outcome."
  3. Replace field-by-field checks with an Expected Object (toEqual/toMatchObject) or a Custom Assertion / verification method that names the concept being verified.
  4. Verify one behavior per test. If a single test is long because it exercises several outcomes (an Eager Test), split it.
  5. Parameterize near-identical verbose tests with test.each / it.each instead of copy-pasting.
// before  — see Signs & Symptoms (≈30 lines of setup + 6 assertions)

// after
test('confirms the order and charges line-item total', () => {
  const customer = aCustomer();                          // Creation Method, defaults
  const cart = aCartWith(aProduct({ price: 9.99 }), 2);  // only the relevant input

  const order = orderService.placeOrder(customer, cart);

  expect(order).toMatchObject({                          // Expected Object, not per-field
    status: 'CONFIRMED',
    total: 19.98,
  });
});

The scenario now reads in seconds: a customer buys two of a $9.99 product → order is confirmed with a $19.98 total. The helpers (aCustomer, aProduct, aCartWith) are shared, tested utilities, so the noise lives in one place instead of every test.

Guardrail: cap test-function length and assertion count in your linter (see detectors) so tests can't silently grow back into Verbose Tests.

##Detected by

  • eslint max-lines-per-functionEnforce a maximum number of lines per function — flags test bodies that exceed the configured limit, the core measure of a Verbose Test.
  • eslint max-statementsEnforce a maximum number of statements in a function block — caps how much a single test (an arrow/function callback) can do.
  • sonar S138Functions should not have too many lines of code — applies to test functions; flags oversized, multi-responsibility test methods (RSPEC-138).
  • eslint-jest jest/max-expectsEnforce a maximum number of expect() calls per test (default 5) — catches the assertion-pile facet of verbose/eager tests.
  • eslint-vitest vitest/max-expectsEnforce a maximum number of expect() calls per test (default 5) — Vitest equivalent of jest/max-expects.