Skip to content

Instantly share code, notes, and snippets.

@MarkKoz
Last active May 11, 2018 05:54
Show Gist options
  • Save MarkKoz/8c776697e82f1596cef983683c218add to your computer and use it in GitHub Desktop.
Save MarkKoz/8c776697e82f1596cef983683c218add to your computer and use it in GitHub Desktop.
Simple unit tester class using Node.js and TypeScript.
import * as assert from "assert";
import * as util from "util";
interface Test {
input: any[];
expected: any;
}
export class Problem {
readonly name: string;
readonly tests: Test[];
readonly enabled: boolean;
/**
* Creates a new testable problem.
*
* @param name The problem's name.
* @param tests The tests to perform on solutions.
* @param enabled true if testing is enabled; false otherwise.
*/
public constructor(name: string, tests: Test[], enabled: boolean = true) {
this.name = name;
this.tests = tests;
this.enabled = enabled;
}
/**
* Tests a solution to the problem.
*
* @param callback The function for the solution.
* @param name The solution's name. Default is the callback's name.
* @param printStack If the stack trace of any exceptions should be
* printed.
*/
public test(callback: (...args: any[]) => any, name?: string, printStack: boolean = false) {
if (!this.enabled)
return;
name = (typeof name !== "undefined") ? name : callback.name;
let failed = 0;
for (let t of this.tests) {
try {
// Lambda to preserve "this" context. Deep copy to prevent callbacks from mutating each other's args.
assert.deepStrictEqual((() => callback.apply(this, Problem.deepCopy(t.input)))(), t.expected);
} catch (e) {
++failed;
console.error(`[${this.name}] ${name}`);
console.error(` Input: ${Problem.inspect(t.input)}`);
console.error(` Expected: ${Problem.inspect(t.expected)}`);
if (e instanceof assert.AssertionError) {
console.error(` Actual: ${Problem.inspect(e.actual)}`);
} else if (e instanceof Error) {
if (printStack)
console.error(Problem.indent(e.stack));
else
console.error(` ${e.name}: ${e.message}`);
} else {
console.error(e);
}
}
}
if (failed)
console.error(`[${this.name}] ${name} - Failed ${failed} out of ${this.tests.length} tests.`);
else
console.log(`[${this.name}] ${name} - Passed all tests!`);
}
/**
* Performs a deep copy on an object.
*
* @param obj The object to copy.
* @returns A copy of the object.
*/
private static deepCopy(obj: any[] | object) : any[] | object {
let v, key;
let output : any[] | object = Array.isArray(obj) ? [] : {};
for (key in obj) {
v = obj[key];
output[key] = (typeof v === "object") ? this.deepCopy(v) : v;
}
return output;
}
/**
* Indents every line of a string with spaces.
*
* @param str The string to indent.
* @param size The amount of chars by which to indent.
* @returns The indented string.
*/
private static indent(str: string, size: number = 4): string {
return str.replace(/^(?!\s*$)/mg, " ".repeat(size));
}
/**
* Gets a string representation of an object.
*
* Determines if the string contains multiple lines. If it does, a newline
* is prepended and every line is indented.
*
* @param obj The string to check.
* @returns The string representation of the object.
*/
private static inspect(obj: any): string {
const str = util.inspect(obj, false, null);
for (let count = -1, index = 0; index !== -1; count++, index = str.indexOf('\n', index + 1))
if (count > 1)
return this.indent("\n" + str, 8);
return str;
}
}
@MarkKoz
Copy link
Author

MarkKoz commented May 11, 2018

Example use:

const problemFoo: Problem = new Problem(
    "A Problem",
    [
        {input: ["hello", "world"], expected: true}, // Arguments are passed as an array.
        {input: ["goodbye", "world"], expected: false}
    ]); // Add false as the third parameter to disable tests. Easier than commenting out all calls to problemFoo.test().

function foo(x: string, y: string) {
    return x === "hello" && y === "world";
}

problemFoo.test("foo", "John Doe"); // Add true as the third parameter to print stack traces of exceptions thrown.

Success Output

[A Problem] John Doe - Passed all tests!

Failure Output

[A Problem] John Doe
    Input: [ 'hello', 'earth' ]
    Expected: true
    Actual: false
[A Problem] John Doe - Failed 1 out of 3 tests.

Stack Trace Output

[A Problem] John Doe
    Input: [ 'hello', 'earth' ]
    Expected: true
    Error: Error!
        at Problem.foo (problemFoo.js:62:15)
        at assert.deepStrictEqual (Problem.js:34:56)
        at Problem.test (Problem.js:34:95)
        at Object.<anonymous> (problemFoo.js:65:12)
        at Module._compile (module.js:643:30)
        at Object.Module._extensions..js (module.js:654:10)
        at Module.load (module.js:556:32)
        at tryModuleLoad (module.js:499:12)
        at Function.Module._load (module.js:491:3)
        at Function.Module.runMain (module.js:684:10)
[A Problem] John Doe - Failed 1 out of 3 tests.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment