Skip to content

Instantly share code, notes, and snippets.

@J-Cake
Last active July 29, 2024 16:17
Show Gist options
  • Save J-Cake/69019276bb1f6226153f22a205117bc1 to your computer and use it in GitHub Desktop.
Save J-Cake/69019276bb1f6226153f22a205117bc1 to your computer and use it in GitHub Desktop.
A NodeJS Test runner for those who don't like the new native one

Test Runner

The NodeJS test runner is kinda whacky. This one is a simple test runner which makes your life easy.

Invoking the test runner is simple:

find ./test -iname *.test.js | xargs -d \\n -I {} node runner.js {}

This example locates all *.test.js files and calls` runner.js for each file.

The following options are supported:

Option Purpose
--log-level [err|info|output|debug] Sets the log level of the test runner. There aren't that many log messages, so this really won't make a huge difference, but it's there.
--return Print the return value of each test after it's done.
--continue-on-failure Will prevent the runner from exiting if a test fails.
import util from 'node:util';
import * as fs from 'node:fs/promises';
import chalk from 'chalk';
const argv = process.argv.slice(2);
const getArg = arg => argv.includes(arg) ? argv.splice(argv.indexOf(arg), 2)[1] : null;
const toggle = arg => argv.includes(arg) ? !(void argv.splice(argv.indexOf(arg), 1)) : false;
const logLevel = getArg("--log-level") ?? "debug";
const exitOnFailure = !toggle("--continue-on-failure");
const printReturn = !toggle("--no-return");
export const stripAnsi = str => str.replace(/[\u001b\u009b][[()#;?]*(?:\d{1,4}(?:;\d{0,4})*)?[\dA-ORZcf-nqry=><]/g, '');
export const centre = (text, width) => {
const colourless = stripAnsi(text);
const pad = Math.floor((width - colourless.length) / 2);
return `${' '.repeat(pad)}${text}${' '.repeat(pad)}`.padStart(width, ' ');
}
export function stdout(tag, ...msg) {
const log = msg
.map(i => ['string', 'number', 'bigint', 'boolean'].includes(typeof i) ? i : util.inspect(i, false, null, true))
.join(' ')
.split('\n')
.map((i, a) => `${a ? centre('\u2502', stripAnsi(tag).length) : tag} ${i}\n`);
for (const i of log)
process.stdout.write(i);
}
export function stderr(tag, ...msg) {
const log = msg
.map(i => ['string', 'number', 'bigint', 'boolean'].includes(typeof i) ? i : util.inspect(i, false, null, true))
.join(' ')
.split('\n')
.map((i, a) => `${a ? centre('\u2502', stripAnsi(tag).length) : tag} ${i}\n`);
for (const i of log)
process.stderr.write(i);
}
export const log = {
err: (...arg) => void (['err', 'info', 'output', 'debug'].includes(logLevel) && stderr(chalk.grey(`[${chalk.red('Error')}]`), ...arg)),
info: (...arg) => void (['info', 'output', 'debug'].includes(logLevel) && stdout(chalk.grey(`[${chalk.blue('Info')}]`), ...arg)),
output: (...arg) => void (['output', 'debug'].includes(logLevel) && stdout(chalk.grey(`[${chalk.yellow('Output')}]`), ...arg)),
debug: (...arg) => void (['debug'].includes(logLevel) && stdout(chalk.grey(`[${chalk.cyan('Debug')}]`), ...arg))
}
log.debug("Running tests:", argv);
for (const path of argv) {
try {
const file = await import(await fs.realpath(path));
for (const [name, fn] of Object.entries(file))
try {
const result = fn();
if (printReturn)
log.output(chalk.green("Success"), chalk.grey(`${path}::${name}`), result);
else
log.info(chalk.green("Success"), chalk.grey(`${path}::${name}`));
} catch (err) {
log.err(chalk.yellow("Failure"), chalk.grey(`${path}::${name}`), err);
if (exitOnFailure)
process.exit(-1);
}
} catch (err) {
log.err(chalk.red("Error"), path, err);
if (exitOnFailure)
process.exit(-1);
}
}
log.info(chalk.green("All tests passed"));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment