A test is a tiny program that checks that another program does what it promises. In Jest it always has the same shape: the test function takes a name and a body - a callback, that is a function Jest will call for you. Inside the body you write expect of the actual value and attach a matcher - a method that states the expectation. The running example of the book is an order service, and the first function under test, calculateDelivery, takes an order and returns the shipping cost. We start with it.
It is best to begin with the smallest honest test, not with the whole framework. Take one rule: delivery costs 300 rubles for a standard order. A three-line test - a name, a function call, an expectation - already documents the rule and protects it from future edits. The name is written as a claim about behavior: 'charges 300 for standard delivery', not 'test of calculateDelivery'.
Let us walk the shape line by line. test is a global function, you need not import it; the first argument is a string name, the second a callback with no arguments. The call expect(calculateDelivery(order)) wraps the actual result in a check object, and toBe(300) is the matcher that compares it to the expected value via Object.is. test has an exact synonym it: the form it('charges ...') reads like an English sentence, and both are interchangeable - the choice is purely stylistic.
When one function gains several tests, they are gathered into a group with describe. The first argument is the name of a system element, usually a function or component; the second is a callback inside which the tests live. The group does not change how the checks behave, but it gives two things: in the report the tests are printed indented under a common heading, and a natural place appears for shared setup via beforeEach.
Running is one command. npx jest finds every file with the .test suffix and runs them, printing a green tick for each passing test and a red cross for a failing one. During development the watch mode is more convenient: npx jest --watch monitors files and reruns only the affected tests - the edit-and-check cycle shrinks to seconds. In CI watch is unnecessary and even harmful; there you run a plain jest.
The key skill for a beginner is to read a failure, not to fear it. When a matcher does not match, Jest prints three things: the name of the failed test, an Expected against Received block, and the exact line with a pointer. Expected is what you wrote in the matcher, Received is what the code actually returned. If it says Expected 300 and Received 250, the question is not about the test but about why the function returned 250. The red output is not a punishment but the address of the discrepancy.
FAIL src/delivery.test.ts
✕ charges 300 for standard delivery
expect(received).toBe(expected) // Object.is equality
Expected: 300
Received: 250
6 | expect(calculateDelivery(order)).toBe(300)
| ^Out of this grows the main rhythm - the red-green loop. First you write a test for behavior that does not yet exist and run it - it must fail red; this proves the test is capable of catching an error at all and does not pass for nothing. Then you write the minimal code to make the test go green - exactly as much as needed, with no reserve for the future. Red before green is not bureaucracy: a test you have never seen red may quietly pass because of a typo in the matcher name.
The price of this discipline is minutes on an extra run and the habit of thinking about the expected result first. The payoff is that every test has proven its ability to fail, and the suite grows as a verified specification of behavior. A typical failure of the reverse approach: a test is written green from the start, never seen red, and a month later it turns out expect stood without a matcher or compared a value to itself - and caught not a single regression.
import { calculateDelivery } from './delivery.js'
test('charges 300 for standard delivery', () => {
const order = { total: 1_000, premium: false }
expect(calculateDelivery(order)).toBe(300)
})