Learning paths / Vibe coding to production / Tests that prove something

Tests that prove something

Reading · 6 min · Module 5, lesson 1 of 317 min left in this module

Module 5 · Tests that prove somethingLesson 1 of 3

Goal: Write a test that fails on the starter's overdue bug, then fix the bug and watch the same test pass.

3:54 · captions and chapters · narrated with an AI-generated voice
Transcript

Narration uses an AI-generated voice.

[00:00] Where we're going

By the end of this video, you'll write a test that fails on the starter's overdue bug. Then you'll fix the bug, and watch the same test pass. Because a test only proves something if it could have failed.

[00:14] The bug

Module one asked whether a task due today counts as overdue. In the starter, it does. And that's a bug. Open the dates file. The last line of isOverdue decides. Both sides are the start of a day. When the due day is today, they're equal. And less than or equal says overdue. The rule should be: due before today. But don't fix it yet.

[00:41] A test that proves nothing

None of the tests look at overdue dates, so they all pass. Now say someone adds a test the quick way. Run the function, and write down whatever it returns. Due today, and the expected answer is true: overdue. That matches the code, bug and all.

Run the tests, and it passes straight away. Before any fix. Eight tests, all green. And the bug is still there. A test that passes against buggy code can't detect the bug.

[01:14] Write the test first

So write the test before the fix. First, add isOverdue to the require line at the top. Then two tests. Each one fixes now to a single moment, so it gives the same answer on any day you run it. The first says a task due today is not overdue. The second says a task due yesterday is overdue.

Why the second one? Picture the line. Yesterday is overdue. Today is not. A lazy fix could just return false, every time. That passes the first test. The second one catches it. So test both sides of the line.

[01:53] Watch it fail

Now run the tests. One fails: a task due today is not overdue. Read the failure before moving on. True, where you expected false. That's exactly the bug, and not a typo in the test.

Compare a failure for a different reason. Leave out the require line, and both tests fail: isOverdue is not defined. That's a failure, but it proves nothing yet. Make the test fail for the reason you expect.

[02:26] Fix it, watch it pass

Now fix it. Change less than or equal, to less than. Run the tests again, and all nine pass. The same test that failed now passes. You saw it fail first, so you know it's checking the rule. Commit the test and the fix together, so the history shows the proof next to the change.

[02:48] Ask for the failing test

When you hand a bug to an agent, ask for the test first, not the fix. Something like this. Write a test that fails because a task due today counts as overdue. Run it and show me the failure. Don't change the dates file yet. You check one small thing, the failure message, before any fix exists to distract you.

[03:12] Recap

To recap. Write the test before the fix. Watch it fail, for the reason you expect. Test both sides of the line. Then fix the code, and watch the same test pass. A test you've never seen fail might not be checking anything.

Here's a question to check yourself. You add a test for a bug, and it passes straight away, before any fix. What does that tell you? That the test doesn't check the bug. Change its input or expected value until it fails for the right reason. Next, check what you've learned. I'll see you there.

Key idea

A test proves something only if it could have failed. Write it before the fix, run it, and watch it fail for the reason you expect. Then fix the code and watch the same test pass. A test you've never seen fail might not be checking anything.

Module 1 asked whether a task due today counts as overdue. In the starter it does, and that's a bug. None of the tests look at overdue dates, so they all pass: exactly the gap an agent's "all tests pass" can hide.

Find the rule

Open lib/dates.js. The last line of isOverdue decides:

return startOfDay(task.due + "T00:00:00Z") <= startOfDay(now);

Both sides are the start of a day. When the due day is today, they're equal, and <= says overdue. The rule should be "due before today".

Don't fix it yet.

Write the test first

Open test/dates.test.js. Add isOverdue to the require line at the top, so it reads const { isValidDueDate, daysUntil, isOverdue } = require("../lib/dates");. Then add two tests at the end:

test("a task due today is not overdue", () => {
  const now = new Date("2026-10-01T15:30:00Z");
  assert.equal(isOverdue({ due: "2026-10-01", done: false }, now), false);
});

test("a task due yesterday is overdue", () => {
  const now = new Date("2026-10-01T15:30:00Z");
  assert.equal(isOverdue({ due: "2026-09-30", done: false }, now), true);
});

Each test fixes now to one moment, so it gives the same answer on any day you run it. The second test guards the other side of the line, so a "fix" that never marks anything overdue can't pass.

Watch it fail

npm test
✖ a task due today is not overdue
  AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:

  true !== false

Read the failure before moving on. It says the function returned true where you expected false: exactly the bug, and not a typo in the test. A test that fails for a different reason, such as a missing import, proves nothing yet.

Fix it and watch it pass

Change <= to < in isOverdue, run npm test again, and all nine tests pass. Commit the test and the fix together, so the history shows the proof next to the change:

git commit -am "fix: a task due today is not overdue"
Why a fixed 'now' instead of the real clock?

A test that uses today's date passes or fails depending on when it runs, and tests near midnight or across time zones flip without any code changing. That's a flaky test, and people learn to ignore flaky tests. isOverdue takes now as an argument for exactly this reason. When your own code reads the clock deep inside, ask your agent to make the time an argument first, as a separate small change.

Ask for the failing test, not the fix

When you hand a bug to an agent, ask for the test first: "Write a test that fails because a task due today counts as overdue. Run it and show me the failure. Don't change lib/dates.js yet." You check one small thing, the failure message, before any fix exists to distract you.

Check yourself

You add a test for a bug, run it, and it passes straight away, before any fix. What does that tell you?
Why add the 'due yesterday is overdue' test as well?