Manual Testing & Debugging
14 min readΒ·Jan 14, 2026
In programming, an error is an unexpected situation where the code cannot continue normally, either because the instructions are invalid or because something goes wrong while running.
Some of these errors can cause the program to produce incorrect results, while others can cause it to crash.
In JavaScript, errors fall into 3 categories:
- Logical errors occur when the program doesn't produce the expected result or behavior, and are a direct consequence of faulty logic implementation made by the developer.
- Syntax errors, also called parsing errors, occur at interpret time and are related to faulty syntax, such as a missing closing brace
}when using anifstatement, for instance. - Runtime errors, also called exceptions, occur during execution and are usually related to invalid user input, network connection failure, division by non numerical values, and so on.
In this lesson, you will learn how to manually test your code's logic, debug its execution, and interpret Node.js errors.
Recognize crash vs wrong output
When running a script, there are two main outcomes when something is wrong:
- The script crashes β most likely a syntax error or a runtime error.
- The script runs but the output is wrong β most likely a logical error.
This distinction matters because you donβt debug these problems the same way.
Read Node.js error reports
In Node.js, when a program encounters a syntax error or a runtime error, it immediately terminates its execution and the error is written to the standard error stream of the terminal.
For example, when running this script:
const total = 10;
console.log(totl);
Node.js will output this error:
$ node script.js
/Users/razvan/nodejs/script.js:3
console.log(totl);
^
ReferenceError: totl is not defined
at Object.<anonymous> (/Users/razvan/nodejs/script.js:3:13)
at Module._compile (node:internal/modules/cjs/loader:1554:14)
at Object..js (node:internal/modules/cjs/loader:1706:10)
at Module.load (node:internal/modules/cjs/loader:1289:32)
at Function._load (node:internal/modules/cjs/loader:1108:12)
at TracingChannel.traceSync (node:diagnostics_channel:322:14)
at wrapModuleLoad (node:internal/modules/cjs/loader:220:24)
at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:170:5)
at node:internal/main/run_main_module:36:49
Node.js v22.14.0
A typical error report contains:
-
The file name and line number where the error happened.
/Users/razvan/nodejs/script.js:3 -
The statement that caused the error.
console.log(totl); ^ -
The error type and message explaining what went wrong.
ReferenceError: totl is not defined -
The stack trace, which is the list of calls that led to the error, whose first line also indicates the column where the error happened. In this example,
script.js:3:13means on the 3rd line at the 13th character.at Object.<anonymous> (/Users/razvan/nodejs/script.js:3:13) at Module._compile (node:internal/modules/cjs/loader:1554:14) at Object..js (node:internal/modules/cjs/loader:1706:10) at Module.load (node:internal/modules/cjs/loader:1289:32) at Function._load (node:internal/modules/cjs/loader:1108:12) at TracingChannel.traceSync (node:diagnostics_channel:322:14) at wrapModuleLoad (node:internal/modules/cjs/loader:220:24) at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:170:5) at node:internal/main/run_main_module:36:49
In practice, the fastest way to debug most crashes is to:
- Read the error type and message.
- Go to the line indicated by Node.js.
- Inspect the values around that line.
Note that JavaScript has several built-in error types, including but not limited to:
SyntaxErrorto indicate a syntax error.TypeErrorto indicate an operation was performed on a value of an inappropriate type.RangeErrorto indicate a numeric value is out of range.ReferenceErrorto indicate an invalid reference.
Fix syntax errors
A syntax error means Node.js cannot parse your file, so the script never starts running.
When this happens, Node.js outputs a SyntaxError in the console pointing to the first error:
$ node syntax_error.js
/Users/razvan/learnbackend/syntax_error.js:5
SyntaxError: Unexpected end of input
at wrapSafe (node:internal/modules/cjs/loader:1486:18)
at Module._compile (node:internal/modules/cjs/loader:1528:20)
at Object..js (node:internal/modules/cjs/loader:1706:10)
at Module.load (node:internal/modules/cjs/loader:1289:32)
at Function._load (node:internal/modules/cjs/loader:1108:12)
at TracingChannel.traceSync (node:diagnostics_channel:322:14)
at wrapModuleLoad (node:internal/modules/cjs/loader:220:24)
at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:170:5)
at node:internal/main/run_main_module:36:49
Node.js v22.14.0
To fix this error:
- Go to the line Node.js points to as indicated by the number after the script name:
syntax_error.js:5. - Look for missing or invalid delimiters such as
},),], quotes, commas, etc. - Fix the syntax, then re-run the script.
Fix logical and runtime errors
Once your file can be executed, you are usually facing either:
- A runtime error: the script starts, then crashes.
- A logical error: the script runs, but the output is wrong.
In both cases, the process is the same:
- Reproduce the problem.
- Isolate what triggers it.
- Print values to verify what the code is actually doing.
Reproduce and isolate the problem
In software development, debugging starts with reproducing the problem consistently. This is done by running the script multiple times while changing inputs or values in a controlled way.
This process is often called manual testing, and it applies to both runtime and logical errors.
Manual testing essentially consists in:
- Running the script.
- Observing the output (or crash).
- Changing one parameter (a value, a condition, an input).
- Running the script again.
- Repeating until you understand what causes the behavior.
π‘ Tip: You should never change more than one parameter per iteration, as it makes it more difficult to identify the faulty statement.
Example
Let's consider this script that decides whether a user can access a feature based on their age:
const age = 17;
if (age >= 18) {
console.log('Access granted');
} else {
console.log('Access denied');
}
A basic manual test would be to run the script multiple times after changing the value of age:
age = 18β expect"Access granted"age = 17β expect"Access denied"age = -1β does the output still make sense?
Debug with logging
When a script behaves incorrectly (or crashes), you often need visibility into what the code is doing.
This is done by printing intermediate values using console.log().
The goal is to confirm simple things such as:
- What value is used right before a condition?
- Which branch is being executed?
- Which value is being used on the line that crashes?
π‘ Tip: Always log with labels like
console.log('age:', age). Unlabeled logs become hard to read as soon as you print more than one value.
Example: debug a runtime error
Let's consider this script:
const total = 10;
console.log('total:', total);
console.log(totl);
When executed, it will:
- Print the value of
total. - Crash on the next line because
totldoes not exist.
$ node runtime_error.js
total: 10
ReferenceError: totl is not defined
The fix is straightforward: use the correct variable name.
const total = 10;
console.log('total:', total);
console.log(total);
Example: debug a logical error
Let's consider this script:
const price = 100;
const hasCoupon = false;
// Expected: should print 100
if (hasCoupon = true) {
console.log(price * 0.8);
} else {
console.log(price);
}
It prints 80, which means the if branch is executed even when hasCoupon is false.
To verify what happens, add logs:
const price = 100;
const hasCoupon = false;
console.log('hasCoupon before if:', hasCoupon);
if (hasCoupon = true) {
console.log('branch: coupon');
console.log(price * 0.8);
} else {
console.log('branch: no coupon');
console.log(price);
}
Now the issue is clear: hasCoupon = true changes the value instead of checking it.
A simple fix is to use the variable directly as the condition:
const price = 100;
const hasCoupon = false;
if (hasCoupon) {
console.log(price * 0.8);
} else {
console.log(price);
}
A debugging checklist
When your script doesn't behave as expected:
- Reproduce the problem consistently.
- If the script crashes, read the error report (type, message, line).
- Add one log right before the suspicious line or condition.
- Re-run the script and observe what changes.
- Fix the root cause, then re-run again.
- Remove logs once the bug is fixed.
If you follow this process, you will be able to debug most beginner scripts without any special tools.
Enjoying the courses?
I've made these courses completely free so anyone can learn from them. If they've helped you and you'd like to actively support the work behind BackendBrewery, you can leave a tip:
Support BackendBrewery