liveBook · Manning

Assertion roulette

As explained in The Art of Unit Testing 3e

A testing anti-pattern in which multiple assertions are placed in one test so that an early failure prevents later assertions from being evaluated or reported.

Assertion roulette happens when one test contains several assertions and an early failure stops later checks from running. The test report then shows only part of the problem, leaving hidden failures undiscovered until someone comments out assertions and reruns the test. That guessing game can create confusion and false confidence about what actually passes. section 2.5.9 section 4.4

The practical concern is not simply that a test has more than one assertion. It is that independent results cannot be reported independently when the first failing assertion aborts execution. A test may appear to identify one defect while another failure remains below the line that threw the error. section 2.5.9 section 4.4

Two checks, one interrupted test

A password verifier can return an error list that should contain one item and include a rule’s reason. Placing both checks in one test means a failure of the error-count check prevents the message check from running: const errors = verifier.verify('any value'); expect(errors.length).toBe(1); expect(errors[0]).toContain('fake reason'); If the first expectation fails, the second result is unknown. section 2.5.9

Split independent expectations into separately named test cases when each result deserves its own diagnosis. One test can verify that the error message contains the rule reason; another can verify that exactly one error was returned. Each test can then fail without blocking the other. section 2.5.9

Listing 2.12 Checking an extra end result from the same exit point
describe('PasswordVerifier', () => {
  describe('with a failing rule', () => {
    it('has an error message based on the rule.reason', () => {
      const verifier = new PasswordVerifier1();
      const fakeRule = input => ({ passed: false,
                                   reason: 'fake reason'});
 
      verifier.addRule(fakeRule);
      const errors = verifier.verify('any value');
 
      expect(errors[0]).toContain('fake reason');
    });
    it('has exactly one error', () => {
      const verifier = new PasswordVerifier1();
      const fakeRule = input => ({ passed: false,
                                   reason: 'fake reason'});
 
      verifier.addRule(fakeRule);
      const errors = verifier.verify('any value');
 
      expect(errors.length).toBe(1);
    });
  });
});

This listing separates the error-message expectation from the error-count expectation, making each outcome visible in its own test result. section 2.5.9

Splitting tests can introduce repeated setup: creating the verifier, defining a fake rule, adding it, and calling verify again. That duplication is a trade-off, not a reason to accept hidden failures. Shared setup such as beforeEach can reduce repetition after the tests have been separated. section 2.5.9

Assertion roulette also matters in interaction testing. If one verification fails, later mock verifications may not run, so several broken interactions can look like a single failure. Seeing the full set of failures helps avoid fixing the wrong behavior or fixing it in the wrong place. section 4.4

Do not confuse assertion roulette with the distinction between mocks and stubs. Assertion roulette describes failure reporting caused by test structure; mocks and stubs describe different kinds of test doubles. Both can appear in the same test, especially when interaction verifications are involved. section 4.4

How The Art of Unit Testing 3e covers it

How deep it goes
Explains early termination, hidden assertions, separate tests, duplication, and setup, without detailing framework internals.
Examples
Uses a PasswordVerifier error list, fake rule, consecutive expectations, and separate named tests.
What it assumes
Introduces the issue through simple test assertions and a fake rule without requiring prior framework knowledge.
What you can do after
Diagnose/debug with it by recognizing masked assertions and separating independently meaningful checks.
How it connects
Connects assertion roulette with unit testing, interaction testing, mocks, stubs, and beforeEach setup.

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.