PLAINFINCH learn by building

Reading a failing test

You will read a failing test report step by step and decide whether the test or the code needs fixing.

What does a failure report tell you, in order?

Every failure in Results has the same parts, and reading them in order saves time. First comes the test's full name, with its describe heading in front: labour › an hour and a half costs 50. That tells you the situation before you see any numbers. Next is the message, which says what the matcher wanted, such as expected 60 to be 50. Then come the Expected and Received boxes side by side, and finally the line in the test file where the failing expect() sits.

Real Vitest prints the same pieces in a terminal: a red FAIL with the file and test name, an AssertionError line, a coloured diff with - Expected and + Received, and a few lines of the test file with an arrow on the failing line. Jest's layout differs a little and holds the same facts. Once you know what each part means, either runner's output reads the same way.

Which value is Expected and which is Received?

Expected is whatever you put in the matcher, the number inside toBe(). Received is whatever the code produced, the value inside expect(). In the labour test, Expected is 50 and Received is 60, so the code returned 60 when the test author thought it should return 50.

That says nothing yet about who is right. A test is a claim about the code, and a failure means the claim and the code disagree. Your next step is to find out which one matches the real rule. Treat the received value as evidence, and work out what the answer should be from the rule, by hand, before you look at the code.

labour › an hour and a half costs 50
expected 60 to be 50
  Expected: 50
  Received: 60
  quote.test.js:10
FigureFailure report for labour › an hour and a half costs 50, read top down: name, message expected 60 to be 50, Expected 50 from toBe, Received 60 from the code, quote.test.js:9.Failure report for labour › an hour and a half costs 50, read top down: name, message expected 60 to be 50, Expected 50 from toBe, Received 60 from the code, quote.test.js:9.
Read a failure top down: name, message, Expected (your claim), Received (what the code did), then the line. Here the rule says 60, so the test is wrong.

How do you decide whether the test or the code is wrong?

Go back to the source of truth. Here it is the comment at the top of quote.js: labour is 40 an hour, and a quote is labour plus every part. At work it might be a ticket, a design or a conversation with the shop owner. An hour and a half at 40 is 60. The code says 60. The test is wrong.

The puncture test claims half an hour plus parts of 6 and 3 makes 29. Twenty for labour, nine for parts: 29 is right. The code says 23. The test is right and the code is wrong. Both decisions came from the rule, and neither came from looking at which file was easier to change.

When there is no written rule, ask. Guessing usually means changing whichever side is closer to hand, and half the time that bakes a bug into the test.

  • Read the test name and the two values
  • Work out the right answer from the rule, by hand
  • If the code matches the rule, fix the test
  • If the test matches the rule, fix the code
  • If nothing says what the rule is, find out before changing either

How does the received value point at the bug?

Once you know the code is wrong, the received value is your best clue to why. The quote came back as 23. Labour for half an hour is 20, so the parts added up to 3, which is the last part on its own. A total that equals only the last item almost always means a loop or reduce is replacing a running value instead of adding to it.

Look at line 10: job.parts.reduce((sum, p) => p, 0). The callback ignores sum and returns the current part, so each step throws away the total so far. Returning sum + p fixes it. The test that passed, a job with no parts, could never have caught this: reduce on an empty array returns the starting value 0 whatever the callback does. That is why the puncture test, with two parts, is needed.

What do errors with no Expected or Received mean?

Sometimes a test fails before its matcher runs. The report then shows an error name and message with no Expected and Received boxes. ReferenceError: hirePrice is not defined means the test used a name that does not exist, often a typo or a missing import. TypeError: Cannot read properties of undefined (reading 'reduce') means something was undefined when the code tried to use it, for example a job object without a parts array.

Read the line number carefully. For a ReferenceError in the test, the line points into the test file and the fix is there. For a TypeError thrown inside the code, the report points into the code file, and you need to decide whether the code should handle that input or the test passed something the code never promised to accept. Either way, a test that crashed has not checked anything yet. Fix the crash first, then read the real result.

What if the whole file fails to load?

A syntax error, such as a missing closing bracket, stops the file from loading at all. No test runs, so Results shows a single red bar with the file name and line in place of the test list. In a terminal, Vitest reports this as a failed suite with zero tests. The line number is where JavaScript gave up, which is often a line or two after the real mistake, so look just above it as well.

The practice runner shows the same message in a red bar under the editor, naming the file and the line. Fix that first: until the file loads, none of the other reports exist.

Why is deleting a failing test the wrong fix?

Deleting or skipping a red test makes the bar green and leaves the bug exactly where it was. Changing the expected value to whatever the code returned is the same move in disguise. Both are tempting at the end of a long change, and both are common in AI-generated fixes, which often report success after "updating the test to match the new behaviour".

Before changing any expected value, write down why the new value is right, using the rule. If the only reason is "the code says so", the test is about to stop protecting you. The check for this lesson counts tests for that reason: all four must still be there at the end.

How do you read failures when many tests break at once?

When one change turns ten tests red, look for what they share. If every quote test fails and every labour test passes, the problem is in quote(), not in labour(). Start with the simplest failing test, the one with the fewest moving parts, because its received value is easiest to explain. Fixing one root cause often turns the rest green together.

Run a single file while you work by passing its name, as in npx vitest quote, and use test.only briefly if you need quiet output. Remove .only before you commit.

Field notes

  • A failure report gives the test name, the message, Expected, Received and the line.
  • Expected is what you wrote in the matcher, and Received is what the code returned.
  • Work out the right answer from the written rule before you change anything.
  • Changing a test to match the code hides bugs instead of fixing them.

Easy to get wrong

Copying the received value into the test records the bug as correct behaviour. Decide the answer from the rule, then fix whichever side disagrees.

Bending the code to satisfy a wrong test breaks every other price in the shop. When the code matches the rule, the test is what needs to change.

A skipped test is a hidden failure. It keeps the bar green while the bug ships.

Questions people ask

Why is the line number sometimes one or two lines off?

The runner reports where the error was thrown. For a failed matcher that is the expect() line. For a syntax error it is where the parser gave up, which can be after the real mistake.

What does the diff with plus and minus signs mean in Vitest?

Lines marked with a minus are in the expected value and missing from the received one; lines marked with a plus are in the received value only. For objects it shows exactly which property differs.

Can both the test and the code be wrong?

Yes, and the Break It challenge has a file where different tests show each case. Check each failing test against the rule separately, and expect to change both files sometimes.

The test passes but I still think the code is wrong. What now?

Write a new test for the case you are worried about, with the answer worked out by hand. If it fails, you have found a bug and kept a test that guards it.

Keep going