Free learning / 35-minute testing lab
Tests 101: understand what a green result means
Define fixtures, assertions and the test runner. Compare six real Node.js runs and learn why filtering or weakening a check can hide a defect.
No account needed. Use your own copy for writing and keep the original as a reference.
Engineering lab library / Run locally
Read the evidence behind a green result.
Predict each result, inspect the exact command and compare which assertions actually ran. Then change the implementation and the filter separately.
Six real Node.js test processes in a new temporary folder, removed after reporting. Fictional tickets; no model, network, package installation or existing-project changes.
Start here / Node.js 24 · Offline exercise
Tests 101 · real Node.js runs
Compare assertion failures, setup errors, filtered execution, a repair and a weaker comparison.
Node.js 24 · Standard library · No model or network
An original 35-minute foundation for technical people who have not written automated tests. Start with a familiar function call, then inspect six real Node.js test runs. You will learn to distinguish an assertion failure, a setup problem and a command that never exercised the behavior you care about. All tickets are fictional.
1. Name the pieces before reading a failure
An automated test is a program that exercises behavior and checks an expectation. Imagine a colleague asks you to check a spreadsheet formula with a few worked examples. You choose inputs, calculate the result and compare it with an independently agreed answer. A software test makes that comparison repeatable. The analogy has a limit: repeating the check cannot tell you whether the agreed answer was sensible in the first place.
A fixture is prepared input or supporting state for a test. An assertion is a check written as code. The test runner selects and executes tests, records failures and prints a report. A test suite is a collection of tests. A regression is previously expected behavior that a change breaks; a regression test preserves an example of that behavior.
This lab contains unit tests: small checks around one ordinary function. An integration test would exercise collaborating components, and an end-to-end test would follow a larger user flow. These terms describe scope, not a guarantee of quality. Unit tests cannot establish that cloud credentials, deployed networking or a real customer workflow work.
For an FDE using a coding agent, a green result is useful evidence only when you can explain the question that was actually checked. The agent can write a test or run a command. You still need to understand its expectations, inputs and scope.
2. Follow input, behavior and comparison
Our fictional rule is simple: North billing tickets may become a draft; foreign tickets and unsupported categories must be blocked. A draft is only an output label in this exercise. It sends nothing and grants no permission.
| Prepared input | Required result | Reason for including it |
|---|---|---|
| North, billing | draft | Preserve legitimate work |
| South, billing | blocked | Catch a missing tenant comparison |
| North, unknown category | blocked | Keep the category restriction |
The baseline function checks category but forgets tenant. Here is one test written out without the lab's reporting machinery:
import test from 'node:test';
import assert from 'node:assert/strict';
function route(ticket) {
return ticket.category === 'billing' ? 'draft' : 'blocked';
}
test('foreign billing stays blocked', () => {
const ticket = { tenant: 'south', category: 'billing' }; // Arrange
const actual = route(ticket); // Act
assert.equal(actual, 'blocked'); // Assert
});Read it as a sentence: “Given this South ticket, call the function, then require the returned value to equal blocked.” The assertion receives actual first and expected second. It fails because the baseline returns draft. A meaningful failure points to a disagreement; it does not by itself prove whether the requirement, fixture or implementation is wrong.
The examples below keep the rule fixed so you can isolate the implementation, command and assertion changes. Select each event and compare the value produced with the comparison performed.
UNDERSTAND THE SYSTEM
What did this test actually establish?
Follow one fictional ticket through an input, function, expectation and assertion. These six views explain the real Node.js lab. A passing result has to be read with the selected cases and the comparison it made.
See what moves and why.
Read the connections in plain English
- 01Test input → Function under test
A selected test calls the function with this prepared ticket.
- 02Function under test → Assertion
The function result reaches the comparison.
- 03Expected behavior → Assertion
The assertion checks the result against the stated behavior.
- 04Assertion → Runner result
An assertion failure is reported for the selected test; the runner also reports setup failures.
Check your understanding: A coding agent reports a green run. What would you inspect before trusting its claim?
Read the exact command and selected test names, confirm the relevant bodies reached meaningful assertions, compare expectations with the requirement and associate the run with the reviewed code. Filtered or weakened tests can pass while the intended behavior is still wrong.
3. Predict and run six local cases
Download fde-test-evidence-lab.mjs into a new working folder. Open it in an editor before running it. Use Node.js 24; the file needs no npm install, model account, API key or network. It creates test files in a new system temporary directory and removes only that directory after collecting the results. It does not load or edit your existing project.
node --version
node fde-test-evidence-lab.mjs > test-evidence.jsonThe outer command produces one JSON report. Its exit code of 0 means the experiment finished collecting results, not that all six child suites passed. Read each case's command, exitCode, executedCases, observations, runner and original stdout. The generated test source is included so you can inspect exactly what ran. Temporary paths in diagnostic output are replaced with <temporary-lab> for readability.
Write your predictions before running:
| Case | What changes | Test bodies observed by the default run | What to explain |
|---|---|---|---|
| baseline | Category check only | 3; two pass, one fails | Foreign actual is draft; expected is blocked |
| filtered | Only the permitted test is selected | 1; it passes | Two refusal cases did not run |
| setup-error | Import points to a deliberately missing module | 0; file loading fails | No behavior assertion was reached |
| repaired | Tenant and category are both checked | 3; all pass | All three supplied examples agree with the rule |
| overcorrected | Function always returns blocked | 3; permitted case fails | Denials alone would miss the broken allowed path |
| weak-assertion | Check only that the output is a string | 3; all pass | The foreign behavior is still wrong |
The runner can report one failed test for the setup-error file even though no ticket test body ran. In the filtered example it can report zero skipped tests while omitting unselected tests. Compare declaredCases with executedCases and notObserved; do not infer execution from the total or skip count alone.
stdout contains the real test report and OBSERVATION lines written immediately before each assertion. These lines are learning instrumentation, not a security boundary. Their absence in setup-error is expected. The report does not execute in this webpage; run the downloaded file to produce your own evidence.
4. Change one thing and compare the evidence
Make two separate copies of the original .mjs file so the effects stay independent.
Experiment A — repair behavior. Change REPAIR_BASELINE = false to REPAIR_BASELINE = true. Run that copy. The baseline should now pass all three contract checks, with foreign actual blocked. The independent setup-error and overcorrected cases remain failures. Explain why the previously filtered green result could not have established this repair.
Experiment B — change selection. In a fresh copy, change INCLUDE_FOREIGN_IN_FILTER = false to INCLUDE_FOREIGN_IN_FILTER = true. Run it. The filtered command now selects permitted and foreign tests. It should expose one pass and one failure without changing the function. The unsupported-category case remains unobserved by that command. State the new scope precisely.
Keep the original report and each edited report under separate filenames. For each, record the changed line, prediction, actual selected names, exit result and observed values. Neither experiment needs a provider, package installation or modifications to your real repository.
5. Review a coding agent's test claim
Ask for the exact command, project version and output, then read what the command includes. A new file does not necessarily join an existing npm test script. In the protected Claude Code starter, the script names test/triage.test.mjs; a separate file needs an explicit run or a reviewed script change. Check that your new test name actually appears.
Use the failure phase to choose the next action. A missing import calls for setup investigation. A failed comparison calls for checking the requirement, input and implementation. A filtered pass calls for examining the missing cases. A pass after weakening expectations calls for reviewing the test change before claiming a repair.
Changing an assertion can be appropriate when the requirement changes, but the decision needs an explanation. The weak-assertion case did not change the rule. It replaced “foreign is blocked” with “the output has a string type.” The original test name remained, which makes the green result especially misleading.
Keep useful allowed examples beside refusal examples. The always-blocked function demonstrates how an overcorrection can make a denial pass while breaking the task. Extend tests around the changed behavior rather than endlessly rerunning unrelated checks. Selectivity is useful when its scope is explicit.
6. Keep your reasoning and continue
Before returning to the coding project, explain all three statements in your own words:
- A process can exit successfully while the behavior you care about was never tested.
- A test can fail before reaching any assertion about application behavior.
- Every selected test can pass after a weaker assertion while a real defect remains.
Your handoff should contain the requirement, reviewed code version, command, executed cases, actual results, both changed-copy reports and remaining uncertainty. A local unit-test pass does not demonstrate authentication, model quality, a deployed integration or authorization to release.
Continue to Use failures to debug the decision in Claude Code training with your existing Academy access. Apply this reasoning to its real starter, confidence boundaries and reviewable regression test.
Primary reference checked 22 October 2026: Node.js 24 test runner, particularly test functions, name filtering and command-line execution. All instruction, examples and fixtures here are original LockedIn Labs material.