Skip to content

Instantly share code, notes, and snippets.

@azz
Last active February 7, 2016 23:57
Show Gist options
  • Save azz/6dadf730b724326eec88 to your computer and use it in GitHub Desktop.
Save azz/6dadf730b724326eec88 to your computer and use it in GitHub Desktop.
Typescript Test Runner
class TestUtils {
// A factory function
static makeTestDecorator(prop: string, modifierFn?: (...fnArgs) => any) {
// Apply modifierFn if it exists, and
// Flatten single-element arrays to a value
const handle = (...values) => {
if (modifierFn)
return modifierFn(...values);
if (values.length === 1)
return values[0];
return values;
}
// A function that produces our decorator
return (...decorations: any[]) => {
// The actual decorator function
return (target, key?) => {
if (key) { // Target is a method
_.extend(target[key], {[prop]: handle(...decorations)})
} else { // Target is a class
// This __test prop is applied to the class object,
// not the instance object.
target.__test = {
[prop]: handle(...decorations)
};
}
}
}
}
}
const TestDecorators = {
bugs: TestUtils.makeTestDecorator('bugs'),
pull: TestUtils.makeTestDecorator('pull'),
it: TestUtils.makeTestDecorator('it'),
describe: TestUtils.makeTestDecorator('describes'),
repro: TestUtils.makeTestDecorator('repro', (steps) => {
return steps
.split('\n')
.map(l => l.trim())
.filter(l => !!l.length)
.join('\n')
})
}
abstract class TestBase {
test: { describes?: string };
constructor() {
// Copy the __test prop from the class to the instance
this.test = this.constructor['__test'];
}
isTest(name: string) {
return /^test/.test(name) && typeof this[name] === "function";
}
}
// a source file
function add(x, y) {
return x - y; // intentional error
}
// in a test file...
const {bugs, pull, repro, it, describe} = TestDecorators;
@describe("add function")
class TestClass extends TestBase {
// ...
@it("should evaluate 0+0 === 0")
public testZeroPlusZero() {
return add(0, 0) === 0;
}
@repro(`
Call add.
Pass two non-zero numbers.
`)
@bugs(1235, 3444)
@pull(343)
@it("should evaluate 1+1 === 2")
public testICanAdd() {
return add(1, 1) === 2;
}
}
// in the test runner...
class TestRunner {
runTests(testInstance: TestBase) {
console.info('* ', testInstance.test.describes);
for (let testName in testInstance) {
if (!testInstance.isTest(testName))
continue;
// run test
let success = !!testInstance[testName]();
// collect metadata
let results = _.extend({}, testInstance[testName].test, {
testName, success, parent: testInstance.parent
});
console[success ? 'log' : 'warn']('\t- ', results.it, results);
}
}
}
// Main
(() => {
let instance = new TestClass;
let runner = new TestRunner;
console.log('Running tests...');
runner.runTests(instance);
})();
@azz
Copy link
Author

azz commented Feb 6, 2016

Running in Chrome

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