Skip to content

Chapter 4 of 7

Writing and Structuring Tests

Well-structured tests follow clear patterns that aid readability and maintenance. The AAA pattern, which stands for Arrange, Act, Assert, divides a test into three phases: Arrange sets up the test data, mocks, and preconditions; Act executes the code under test; and Assert verifies the expected outcome. For example, a calculator test might arrange by instantiating a Calculator, act by calling add(2, 3), and then assert the result equals 5. This simple structure makes the intent of each test obvious and reduces accidental complexity.

Assertions are the heart of every test, and matchers are the methods that define how values are compared. Common matchers include toBe for strict equality, toEqual for deep equality, toContain for checking that an array or string includes a value, toThrow for verifying that an exception is raised, toBeGreaterThan for numeric comparisons, and toMatch for pattern matching. Frameworks such as Jest, Chai, AssertJ, and Hamcrest provide these matchers in different languages. A subtle but important distinction is between assertEquals, which checks value equality, and assertSame, which checks reference equality. Two separately constructed String objects with identical content would satisfy assertEquals but fail assertSame. In Jest, toBe corresponds roughly to assertSame, while toEqual resembles assertEquals.

Several techniques further reduce duplication and broaden coverage. Parameterized testing runs the same test logic with different input and output pairs, allowing frameworks like Jest's test.each, JUnit's @ParameterizedTest, or pytest's @pytest.mark.parametrize to multiply coverage without multiplying code. Snapshot testing captures the output of a component or function and compares subsequent runs against a stored reference, a popular technique for UI components in React with toMatchSnapshot. Although useful, snapshot tests can become brittle if overused because any unintended visual change triggers a failure. Property-based testing takes a different angle: instead of specific examples, the developer defines properties, or invariants, that the code must satisfy for all inputs, and the framework generates hundreds or thousands of random test cases, shrinking any failing input to the minimal example. Tools such as QuickCheck, Hypothesis, and fast-check embody this approach.

All chapters
  1. 1Test Types and Strategy
  2. 2TDD and BDD
  3. 3Test Doubles
  4. 4Writing and Structuring Tests
  5. 5Test Infrastructure and Frameworks
  6. 6Measuring Test Quality
  7. 7Specialized Testing and CI/CD

Drill it

Reading is not remembering. These come from the Testing Tdd deck:

Q

What is a unit test?

A unit test verifies the behavior of a single, isolated piece of code (e.g., a function or method). Key characteristics:Tests one logical unit in isolationDepen...

Q

What is an integration test?

An integration test verifies that multiple components work correctly together. Unlike unit tests, it exercises real interactions:Tests communication between mod...

Q

What is an end-to-end (E2E) test?

An E2E test simulates real user workflows through the entire application stack. It validates the system from the user's perspective:Uses tools like Playwright,...

Q

What is the Test Pyramid?

The Test Pyramid is a testing strategy that recommends:Base (most): Unit tests — fast, cheap, numerousMiddle: Integration tests — moderate speed and countTop (f...