Testing foundations

Arrange, act, and assert

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

A test is an explanation that runs. Someone reading it should see the situation, the action, and the result without digging through a big fixture.

The pattern I use is arrange, act, assert. Set up the minimum state. Do one thing. Check what happened.

The three steps

Here’s a test for the Books API validator. It rejects a book whose author is blank:

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 comments are optional. I usually leave them out once the habit sticks. What matters is the separation.

Arrange explains why this situation matters: an author made of spaces. Act is the one operation that changes something. Assert checks what a caller of validateBook() can observe.

Watch the assert step. If it checked that input.author is blank, it would prove the setup, not the function. The assertion has to look at result.

Don’t hide the important part

Shared setup is tempting. A global fixture with ten books makes every test two lines shorter. It also hides the reason a test exists.

When that test fails, you open the fixture, find the book that matters, and reconstruct what the test was about. My rule: keep shared setup for details that don’t matter to the test, and put the values that carry the risk right in the test body. The blank author belongs in the test, not in a fixture file.

How many assertions?

Several assertions are fine when they describe one outcome. The status, the content type, and the body of one HTTP response belong together. They all say “this response is correct”.

Split the test when the assertions can fail for unrelated reasons. If one assertion fails because creating a book broke and another fails because deleting a different book broke, that’s two tests pretending to be one. The failure message would tell you “the CRUD test failed”, which is no help.

Try this: take one broad Books API test and rewrite it as three focused ones. Create a book, reject invalid input, and ask for a missing book. In each one, underline the single action and check that every assertion looks at its result, not at the arrangement.

Lesson completed