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
arrangeblock 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):
- 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.
- Default the irrelevant data inside the helper, signalling "these values don't affect the outcome."
- Replace field-by-field checks with an Expected Object (
toEqual/toMatchObject) or a Custom Assertion / verification method that names the concept being verified. - Verify one behavior per test. If a single test is long because it exercises several outcomes (an Eager Test), split it.
- Parameterize near-identical verbose tests with
test.each/it.eachinstead 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-function — Enforce a maximum number of lines per function — flags test bodies that exceed the configured limit, the core measure of a Verbose Test.
- eslint max-statements — Enforce a maximum number of statements in a function block — caps how much a single test (an arrow/function callback) can do.
- sonar S138 — Functions should not have too many lines of code — applies to test functions; flags oversized, multi-responsibility test methods (RSPEC-138).
- eslint-jest jest/max-expects — Enforce a maximum number of expect() calls per test (default 5) — catches the assertion-pile facet of verbose/eager tests.
- eslint-vitest vitest/max-expects — Enforce a maximum number of expect() calls per test (default 5) — Vitest equivalent of jest/max-expects.