Test code is mostly decisions. Did the request return 200? Did the response come back fast enough? Did the response body contain what we expected? Each of those is a yes/no question — and JavaScript's if/else statement is how you ask them. This lesson teaches the syntax, the patterns you'll use every day, and the structural pitfalls beginners hit.
The basic if
if runs a block of code only when a condition is true.
const statusCode = 200;
if (statusCode === 200) {
console.log("Test passed");
}Output:
Test passed
If the condition is false, the block is skipped. Try running with statusCode = 500 and the script prints nothing.
=== is strict equality (the next lesson covers it in detail — for now, just use it everywhere). The condition lives inside (). The body lives inside {}. Both are required.
Adding else
else runs when the if condition is false. It's how you write pass/fail logic.
const statusCode = 500;
if (statusCode === 200) {
console.log("✅ Test passed");
} else {
console.log("❌ Test failed");
}Output:
❌ Test failed
Exactly one of the two branches runs. There's no scenario where both run, and no scenario where neither runs.
else if for multiple branches
When you have more than two outcomes, chain else if. Each condition is checked in order; the first one that's true runs, and the rest are skipped.
A real example: HTTP status codes fall into ranges that mean different things (200s = success, 300s = redirects, 400s = client error, 500s = server error). A small helper that classifies a response:
const statusCode = 404;
if (statusCode >= 200 && statusCode < 300) {
console.log("success");
} else if (statusCode >= 300 && statusCode < 400) {
console.log("redirect");
} else if (statusCode >= 400 && statusCode < 500) {
console.log("client error");
} else if (statusCode >= 500 && statusCode < 600) {
console.log("server error");
} else {
console.log("unexpected status code");
}Output:
client error
The order matters. JavaScript checks each condition top-to-bottom and stops at the first hit. The trailing else is your safety net for anything that didn't match above — useful for catching API responses with unexpected codes.
Always use curly braces
JavaScript lets you skip the {} if there's only one line in the body:
// Legal but dangerous
if (statusCode === 200) console.log("passed");Don't. Add a second line later — say, increment a counter — and forget to add braces, and you've created a silent bug:
if (statusCode === 200)
console.log("passed");
testsPassed++; // ← always runs, regardless of statusCodeThe indentation makes the second line look like it's inside the if. It isn't. Always use braces, even for one-liners. ESLint can enforce this with the curly rule.
A real check — response time within SLA
Two things at once: the response time has to be acceptable AND we should know how close to the limit we got.
const responseTime = 1750; // milliseconds
const maxAllowed = 2000;
if (responseTime < maxAllowed) {
console.log(`✅ Response time OK (${responseTime}ms)`);
} else {
console.log(`❌ Response time exceeded SLA (${responseTime}ms / ${maxAllowed}ms)`);
}Output:
✅ Response time OK (1750ms)
This is the shape of about 80% of test assertions you'll write — compute a value, compare against a threshold, branch on the result.
Decision flow for a full test
A real test has multiple checks layered together. Status code, then response time, then body shape — each step can fail the test.
Avoid deep nesting — use early returns
A naive translation of the diagram into nested ifs:
function check(response) {
if (response.statusCode === 200) {
if (response.time < 2000) {
if (response.body.userId) {
return "✅ pass";
} else {
return "❌ no userId";
}
} else {
return "❌ slow";
}
} else {
return "❌ bad status";
}
}That works, but the indentation marches off the right edge of the screen — and the important line (return "✅ pass") is buried four levels deep. Prefer an early-return style: deal with each failure case first, then write the happy path at the bottom.
function check(response) {
if (response.statusCode !== 200) return "❌ bad status";
if (response.time >= 2000) return "❌ slow";
if (!response.body.userId) return "❌ no userId";
return "✅ pass";
}
console.log(check({ statusCode: 200, time: 1500, body: { userId: 42 } }));Output:
✅ pass
Same logic, half the nesting. Reading top-to-bottom you can see "all the ways this fails, then the success." That pattern scales — you can add new failure conditions without re-indenting any existing code.
⚠️ Common mistakes
- Using
=instead of==or===.if (statusCode = 200)assigns 200 tostatusCodeand then evaluates as truthy — so theifalways runs. Always use===for comparison. Most linters flag the assignment-in-condition pattern; turn that rule on. - Skipping braces, then adding a line. The silent bug from a forgotten
{}is one of the most common beginner errors. Always brace, even if it's one line today. - Re-checking the same condition. Beginners sometimes write
if (x === 200) { ... } else { if (x !== 200) { ... } }— the inner check is redundant becauseelsealready means "the if was false." Trust JavaScript to do this for you.
🎯 Practice task
Build a status code classifier. 15-20 minutes.
- In your
js-for-qafolder, createclassify.js. - Declare
const statusCode = 404;(you'll change this value as you test). - Write an
if/else if/elsechain that prints:"success"for 200-299"redirect"for 300-399"client error"for 400-499"server error"for 500-599"unexpected status code"for anything else
- Run with
node classify.jsand verify it prints"client error". - Change
statusCodeto200,301,503, and999and rerun each time. Confirm the output matches. - Stretch: wrap the logic in a function named
classify(code)that returns the string instead of logging it. Use early-return style — onereturnper branch, noelse. Call it for several status codes andconsole.logeach result.
The next lesson goes deep on the comparison operators (===, >=, etc.) and the logical operators (&&, ||, !) you'll use to build richer conditions.