Testing foundations

Arrange, act, and assert

Structure each example so its setup, one meaningful action, and expected observable result are easy to read.

8 minute lesson

~~~

Tests are executable explanations. A reader should see the situation, action, and consequence without reverse-engineering a large fixture.

Arrange the minimum relevant state, perform the behavior, then assert the result. Several assertions are fine when they describe one outcome. Split a test when it performs unrelated actions or could fail for unrelated reasons.

test('rejects a book without an author', () => {
  // Arrange
  const input = { title: 'Dune', author: '  ' }

  // Act
  const result = validateBook(input)

  // Assert
  assert.deepEqual(result, {
    ok: false,
    field: 'author',
    reason: 'required'
  })
})

The important separation is conceptual, not the comments. Arrange explains why this situation matters. Act contains one behavior-changing operation. Assert checks what a caller can observe. If the assertion checks only that input.author is blank, it proves the setup, not validateBook().

Avoid hiding important behavior in shared setup. A global fixture containing ten books may make every test shorter, but it also makes the cause of a failure harder to see. Keep common setup for genuinely irrelevant details and put risk-relevant values in the test.

Multiple assertions belong together when they describe one contract, such as the status, header, and body of one HTTP response. Split them when one assertion can fail because creating a book broke and another because deleting a different book broke.

Rewrite one broad Books API test into focused create, reject-invalid-input, and missing-book cases. For each case, underline the single action and verify that every assertion observes its result rather than restating the arrangement.

Lesson completed

Take this course offline

Get every free book and course as PDF and EPUB files.

Get the download library →