The first time you write the same six lines twice in two test files, you've discovered functions. A function is a named, reusable block of code — write it once, give it a name, and call it everywhere you need that logic. Functions are how test code stops being a wall of repetition and starts being a kit of parts. This lesson teaches the syntax for writing and calling functions, the difference between functions that do things and functions that return things, and the naming conventions that make a test suite readable in six months.
Why functions matter
In a test suite, the same logic appears everywhere — log in as a user, build a request URL, format a result line. Without functions you copy-paste those blocks; with functions you write them once.
Three benefits in QA work specifically:
- DRY (don't repeat yourself). One bug fix, one place to fix it. Six copies of the login flow means six chances to forget to update one of them.
- Readable test bodies. A test that reads
loginAs(admin); openOrder(123); cancelOrder();is shorter and clearer than 30 lines ofcy.get(...).type(...).click(...)calls. - Reusable utilities. A
generateTestEmail()helper is something every test in your suite eventually wants.
Declaring a function
The classic syntax — the one you'll meet most often — uses the function keyword:
function checkStatus(code) {
if (code === 200) {
console.log("✅ Status OK");
} else {
console.log(`❌ Status ${code}`);
}
}The parts:
function— the keyword that tells JavaScript you're defining a function.checkStatus— the name. Use this to call it later.(code)— the parameter list.codeis a placeholder; whatever you pass in becomes its value inside the function.{ ... }— the body. This code runs when the function is called.
Calling a function
Once defined, a function does nothing on its own. You call it (or invoke it) by writing its name followed by parentheses, with any arguments inside.
checkStatus(200); // ✅ Status OK
checkStatus(404); // ❌ Status 404The parentheses are mandatory — even when there are no arguments. checkStatus (without parentheses) is the function itself (a value you can pass around); checkStatus() is the call (the act of running it).
Functions with no parameters
Some functions don't need any input — they generate or read something on their own. The parentheses stay, just empty:
function getTimestamp() {
return Date.now();
}
console.log(getTimestamp());Output (yours will differ):
1715000000000
Date.now() is a built-in function that returns the current time as a number of milliseconds since 1970. Useful for generating unique-per-run test data.
Side effects vs return values
There are two flavours of function, and the distinction matters in test code.
Side-effect functions do something — print to the console, click a button, write a file. They typically don't return anything useful.
function logFailure(testName) {
console.log(`[FAIL] ${testName}`);
}
logFailure("login"); // [FAIL] loginReturn-value functions compute something and hand the result back with return. They don't usually print or click; the caller decides what to do with the answer.
function formatTestResult(name, status, durationMs) {
return `${name}: ${status} (${durationMs}ms)`;
}
const line = formatTestResult("login", "PASS", 124);
console.log(line);Output:
login: PASS (124ms)
Notice the function itself never prints — it only returns. The caller (console.log(line)) does the printing. That separation makes the function reusable: you can later log the line, write it to a file, send it over the network, or build it into a longer report — without changing the function.
A real test data helper
A common QA need: generate unique email addresses for test users so tests don't collide.
function generateEmail(prefix) {
const stamp = Date.now();
return `${prefix}_${stamp}@test.com`;
}
console.log(generateEmail("admin"));
console.log(generateEmail("member"));Output (yours will differ):
admin_1715000000000@test.com
member_1715000000001@test.com
Each call returns a fresh string. Run the tests twice and you get two new sets of emails — no clashes, no flakes from shared state.
Function input → processing → output
That picture is every function. A response goes in, a verdict comes out. The body is whatever logic gets you from one to the other.
Naming conventions
A function name is the most important documentation in your test suite. The convention is verb + noun in camelCase:
validateResponse— verb (validate) + noun (response)createUser— does one thing, says exactly whatgetTestData— fetches and returnsformatReportLine— produces a valueclearCache— performs an action
Boolean-returning functions read naturally with an is, has, or should prefix:
isLoggedIn(user)hasAdminRole(user)shouldRetry(error)
Avoid vague names:
data()— what data?process()— process what, into what?doStuff()— be specific or you're just creating a black box.
A test that reads if (shouldRetry(error)) await retry() is self-explanatory; one that reads if (check(error)) f() is not.
⚠️ Common mistakes
- Forgetting the parentheses when calling.
checkStatus(200)calls the function;checkStatus 200is a syntax error andcheckStatus, 200does nothing useful. Always include(), even for no-argument calls. - Mixing side effects and return values in one function. A function that both prints AND returns the same string can be hard to compose later. Pick one job per function — either it returns a value (let the caller decide what to do with it), or it performs an action.
- Vague names. A function called
validatecould validate anything.validateResponse,validateLoginPayload,validateOrderTotal— the more specific the name, the more useful the function is in search results six months later.
🎯 Practice task
Build a small set of test helpers. 15-20 minutes.
- In your
js-for-qafolder, createhelpers.js. - Write a function
formatTestResult(name, status, durationMs)that returns a string in the form"login: PASS (124ms)". - Write a function
generateEmail(prefix)that returnsprefix_<timestamp>@test.com. - Write a function
isPass(result)that returnstrueonly whenresult.status === "pass"(boolean naming!). - At the bottom of the file, call each function several times and
console.logthe results. - Run with
node helpers.js. Confirm each helper produces the expected output. - Stretch: add a
summarise(results)function that takes an array of{ name, status }objects and returns a string like"3/5 passed (60%)". Use the array'slengthandfilter(you'll formalise array methods in chapter 4 — for now, search forArray.prototype.filterand try it).
Functions are the building blocks of every test suite. The next lesson goes deeper into parameters, default values, and the return keyword.