Software Testing Best Practices

Chapter 2 · Practice 5 of 5

Test boundaries and failure paths, not only the happy path

From Writing good unit tests

Why it matters

Most bugs hide at the edges: off by one errors, empty inputs, nulls, the exact limit and the value just past it. A suite that only tests the happy path will pass while production breaks.

For each function, go through this short list:

  1. Empty: empty string, empty list, zero, missing optional field
  2. Boundaries: the minimum, the maximum, one below and one above each
  3. Invalid: wrong type, malformed input, negative numbers where only positives make sense
  4. Failures: the dependency throws, times out or returns an error

Check that errors are specific. Asserting only that "something threw" passes even when the code throws for the wrong reason. Check the error type or message.

Example

TypeScript
it('rejects an empty cart', async () => {
  await expect(checkout({ items: [] })).rejects.toThrow('Cart is empty');
});

it('accepts exactly the maximum quantity', () => {
  expect(() => addItem(cart, { qty: 99 })).not.toThrow();
});

it('rejects one more than the maximum quantity', () => {
  expect(() => addItem(cart, { qty: 100 })).toThrow(RangeError);
});