The previous lesson introduced functions. This one zooms in on the bits that move data in and out of them — the parameters that name what comes in, the arguments that supply real values, and the return statement that hands a result back. Get these right and your test helpers stay small, focused, and easy to compose.
Parameters vs arguments
Two words for two related-but-different things:
- Parameters are the named placeholders in the function definition.
- Arguments are the actual values you pass at the call site.
function validateResponse(statusCode, body) { // statusCode, body — parameters
console.log(statusCode, body);
}
validateResponse(200, { userId: 42 }); // 200, { userId: 42 } — argumentsMost people use the words interchangeably in conversation, and that's fine. The distinction matters when you read errors or library documentation: "expected 2 arguments, got 1" means the call site is missing a value, not the definition.
Multiple parameters
Functions can take any number of parameters, separated by commas. Inside the body, they're regular variables.
function validateResponse(statusCode, body, responseTimeMs) {
if (statusCode !== 200) return "❌ bad status";
if (!body) return "❌ empty body";
if (responseTimeMs > 2000) return "❌ too slow";
return "✅ pass";
}
console.log(validateResponse(200, { userId: 42 }, 1450));Output:
✅ pass
Order matters — argument number 1 binds to parameter 1, argument number 2 to parameter 2, and so on. Pass them in the wrong order and you get plausible-but-wrong behaviour with no syntax error.
Default parameters
Some parameters have a sensible default. JavaScript lets you set one with = in the parameter list — the default kicks in only when the caller omits the argument or passes undefined.
function login(username, password = "Test@1234") {
console.log(`Logging in as ${username} with ${password}`);
}
login("alice"); // uses default password
login("bob", "MyOwnPassword!"); // uses provided passwordOutput:
Logging in as alice with Test@1234
Logging in as bob with MyOwnPassword!
Defaults are everywhere in test helpers — a default base URL, a default timeout, a default test password used by every fixture. They keep call sites short and let one helper serve every test.
The return keyword
return does two things at once: it sends a value back to the caller, and it ends the function immediately. Anything after a return in the same execution path doesn't run.
function calculatePassRate(passed, total) {
if (total === 0) return 0; // guard against divide-by-zero
return (passed / total) * 100;
}
console.log(calculatePassRate(28, 32)); // 87.5
console.log(calculatePassRate(0, 0)); // 0Output:
87.5
0
A function without a return (or one that hits the end of the body) returns undefined — the same undefined you met in lesson 4 of chapter 1.
function logIt(msg) {
console.log(msg);
}
const result = logIt("hello");
console.log(result); // undefinedThat's why beginners sometimes get confused: "I called the function but result is undefined." The function never returned anything; it only had a side effect.
Guard clauses with early returns
A guard clause is an early return that handles an edge case at the top of the function, leaving the main logic uncluttered. It's the same early-return pattern from the if/else lesson — applied to function bodies.
function getUserName(user) {
if (!user) return "guest";
if (!user.name) return "anonymous";
return user.name;
}
console.log(getUserName({ name: "Alice" })); // Alice
console.log(getUserName({})); // anonymous
console.log(getUserName(null)); // guestOutput:
Alice
anonymous
guest
Each guard kicks the bad input out the front door; the happy path lives at the bottom of the function, never indented past the first level. That style scales — you can add new guards without re-shuffling existing logic.
Storing the return value
Functions that return values are most useful when you capture the result. Store it in a variable; pass it to another function; print it; whatever the test needs.
function buildApiUrl(base, endpoint, query = "") {
const tail = query ? `?${query}` : "";
return `${base}${endpoint}${tail}`;
}
const url = buildApiUrl("https://api.staging.com", "/users", "role=admin");
console.log(url);Output:
https://api.staging.com/users?role=admin
The third parameter has a default of "", so callers who don't need a query string can skip it: buildApiUrl(base, "/health") returns https://api.staging.com/health.
Data flow through a function
Step 1 of 5
Caller passes arguments
validateResponse(200, body, 1450) — three real values are supplied at the call site.
Every function call follows this five-step dance. Once you can see the dance, debugging "why did my function return undefined?" gets a lot faster.
A complete example
Putting parameters, defaults, guards, and return values together:
function summariseRun(passed, failed, skipped = 0) {
const total = passed + failed + skipped;
if (total === 0) return "No tests were run";
const passRate = ((passed / total) * 100).toFixed(1);
return `${passed}/${total} passed (${passRate}%) — ${failed} failed, ${skipped} skipped`;
}
console.log(summariseRun(28, 4)); // skipped defaults to 0
console.log(summariseRun(28, 4, 2)); // explicit skipped count
console.log(summariseRun(0, 0, 0)); // guard branchOutput:
28/32 passed (87.5%) — 4 failed, 0 skipped
28/34 passed (82.4%) — 4 failed, 2 skipped
No tests were run
That's a fully reusable test summary helper — every part you've learned so far in one function.
⚠️ Common mistakes
- Forgetting
return. A function that's supposed to return a value but doesn't will silently returnundefined. The bug shows up at the call site ("why isresultundefined?") and you'll spend twice as long looking there as you should at the function. - Argument order mistakes.
calculatePassRate(total, passed)instead ofcalculatePassRate(passed, total)— both are numbers, no syntax error, wildly wrong percentage. Match the parameter order in the definition. For functions with many parameters, an options object (function f({ passed, failed, skipped })) is often clearer. - Defaults that aren't safe.
function fetchUser(id = 1)defaults to user 1 — fine in test fixtures, dangerous in code that might run in production. Pick defaults that are safe everywhere the function might be called.
🎯 Practice task
Build a tiny test reporting library. 15-20 minutes.
- In your
js-for-qafolder, createreport.js. - Write
calculatePassRate(passed, total)with a guard fortotal === 0. - Write
buildApiUrl(base, endpoint, query = "")like the example above. - Write
summariseRun(passed, failed, skipped = 0)that returns the formatted string. - Call each function with a few different inputs and
console.logeach result. - Run with
node report.jsand confirm the outputs match what you expect. - Stretch: add a
wasFlaky(passed, total, threshold = 0.95)boolean function that returnstruewhen the pass rate is betweenthresholdand1.0exclusive — i.e., it mostly passed but isn't a stable green. Try a few inputs.
The next lesson introduces a shorter way to write functions — arrow functions — which you'll see everywhere in modern test code.