Skip to content

Instantly share code, notes, and snippets.

@Zodiase
Last active February 27, 2016 10:58
Show Gist options
  • Save Zodiase/6d9c8f75827af5cc0a3b to your computer and use it in GitHub Desktop.
Save Zodiase/6d9c8f75827af5cc0a3b to your computer and use it in GitHub Desktop.
This is a light weight tester for NPM.

Modules

EzTest

Classes

EzTest

EzTest

Summary: This is a light weight tester for NPM.
Version: 0.0.1
Author: Xingchen Hong

EzTest

Kind: global class
Since: 0.0.1

new EzTest()

Creates a new test set.

ezTest.addTest(title, func)

Adds a sync test.

Kind: instance method of EzTest
Since: 0.0.1

Param Type Description
title string The title of the test. Keep it shorter than 80 characters.
func SyncTest The test function to be executed. It should return true to mark the test as passed.

ezTest.addAsyncTest(title, func, [timeout])

Adds an async test.

Kind: instance method of EzTest
Since: 0.0.1

Param Type Default Description
title string The title of the test. Keep it shorter than 80 characters.
func AsyncTest The test function to be executed.
[timeout] number 0 Time in milliseconds to wait until failing the test. 0 means no timeout.

ezTest.runTests(finalResultCallback, [singleResultCallback], [haltWhenFailed]) ⇒ integer

Starts all the tests.

Kind: instance method of EzTest
Returns: integer - Test count.
Since: 0.0.1

Param Type Default Description
finalResultCallback TestSetResultCallback
[singleResultCallback] SingleTestResultCallback NOOP
[haltWhenFailed] boolean false Pass true to stop the test set when any test fails.

EzTest~SyncTest ⇒ boolean

Sync test function.

Kind: inner typedef of EzTest
Returns: boolean - true to pass the test. Otherwise the test is failed.
Since: 0.0.1

EzTest~AsyncTest : function

Async test function.

Kind: inner typedef of EzTest
Since: 0.0.1

Param Type Description
pass function Call this to mark the test as passed.
fail function Call this to mark the test as failed.

EzTest~TestSetResultCallback : function

Callback when all tests are run.

Kind: inner typedef of EzTest
Since: 0.0.1

Param Type Description
passed integer How many tests are passed.
total integer How many tests are run. This will be smaller than the total test count if the test run stopped for an exception.

EzTest~SingleTestResultCallback : function

Callback when a single test is run.

Kind: inner typedef of EzTest
Since: 0.0.1

Param Type Description
title string Title of the test.
passed boolean true if the test is passed.
duration number Time in milliseconds spent in the test. -1 when test throws.
async boolean true if the test is async.
timeout boolean true if the test failed due to timeout.
/**
* @author Xingchen Hong
* @version 0.0.1
* @summary This is a light weight tester for NPM.
* @module EzTest
**/
/**
* Creates a new test set.
* @class
* @alias EzTest
* @since 0.0.1
*/
var EzTest = function () {
this._testCount = 0;
this._passCount = 0;
this._runCount = 0;
this._tests = [];
this._running = false;
this._testIndex = -1;
};
EzTest._CONST = {
NOOP: function () {},
TitleTypeErrorMsg: 'Title has to be a string.',
TitleEmptyErrorMsg: 'Title can not be empty.',
TitleMaxLength: 80,
TitleSizeOverflowErrorMsg: 'Keep title within 80 characters.',
TestTypeErrorMsg: 'Test function has to be a function.',
TimeoutTypeErrorMsg: 'Timeout has to be a number.',
TimeoutRangeErrorMsg: 'Timeout has to be a positive number.',
NoResultCallbackErrorMsg: 'Final result callback is required.',
CallbackTypeErrorMsg: 'Expecting a function.',
AlreadyRunErrorMsg: 'Tests are already running.',
AddTestWhileRunningErrorMsg: 'Can not add test while running.'
};
/**
* Adds a sync test.
* @function
* @param {string} title The title of the test. Keep it shorter than 80 characters.
* @param {EzTest~SyncTest} func The test function to be executed. It should return `true` to mark the test as passed.
* @since 0.0.1
*/
EzTest.prototype.addTest = function (title, func) {
if (typeof title !== 'string') {
throw new TypeError(EzTest._CONST.TitleTypeErrorMsg);
}
if (title.length <= 0) {
throw new RangeError(EzTest._CONST.TitleEmptyErrorMsg);
}
if (title.length > EzTest._CONST.TitleMaxLength) {
throw new RangeError(EzTest._CONST.TitleSizeOverflowErrorMsg);
}
if (typeof func !== 'function') {
throw new TypeError(EzTest._CONST.TestTypeErrorMsg);
}
if (this._running) {
throw new Error(EzTest._CONST.AddTestWhileRunningErrorMsg);
}
this._tests.push({
title: title,
func: func
});
this._testCount++;
};
/**
* Sync test function.
* @callback EzTest~SyncTest
* @returns {boolean} `true` to pass the test. Otherwise the test is failed.
* @since 0.0.1
*/
/**
* Adds an async test.
* @function
* @param {string} title The title of the test. Keep it shorter than 80 characters.
* @param {EzTest~AsyncTest} func The test function to be executed.
* @param {number} [timeout=0] Time in milliseconds to wait until failing the test. 0 means no timeout.
* @since 0.0.1
*/
EzTest.prototype.addAsyncTest = function (title, func, timeout) {
if (typeof title !== 'string') {
throw new TypeError(EzTest._CONST.TitleTypeErrorMsg);
}
if (title.length <= 0) {
throw new RangeError(EzTest._CONST.TitleEmptyErrorMsg);
}
if (title.length > EzTest._CONST.TitleMaxLength) {
throw new RangeError(EzTest._CONST.TitleSizeOverflowErrorMsg);
}
if (typeof func !== 'function') {
throw new TypeError(EzTest._CONST.TestTypeErrorMsg);
}
if (typeof timeout === 'undefined') {
timeout = 0;
}
if (typeof timeout !== 'number') {
throw new TypeError(EzTest._CONST.TimeoutTypeErrorMsg);
}
if (timeout < 0) {
throw new RangeError(EzTest._CONST.TimeoutRangeErrorMsg);
}
if (this._running) {
throw new Error(EzTest._CONST.AddTestWhileRunningErrorMsg);
}
this._tests.push({
title: title,
async: true,
timeout: timeout,
func: func
});
this._testCount++;
};
/**
* Async test function.
* @callback EzTest~AsyncTest
* @param {function} pass Call this to mark the test as passed.
* @param {function} fail Call this to mark the test as failed.
* @since 0.0.1
*/
/**
* Starts all the tests.
* @function
* @param {EzTest~TestSetResultCallback} finalResultCallback
* @param {EzTest~SingleTestResultCallback} [singleResultCallback=NOOP]
* @param {boolean} [haltWhenFailed=false] Pass `true` to stop the test set when any test fails.
* @returns {integer} Test count.
* @since 0.0.1
*/
EzTest.prototype.runTests = function (finalResultCallback, singleResultCallback, haltWhenFailed) {
if (typeof finalResultCallback !== 'function') {
throw new TypeError(EzTest._CONST.NoResultCallbackErrorMsg);
}
if (typeof singleResultCallback === 'undefined') {
singleResultCallback = EzTest._CONST.NOOP;
}
if (typeof singleResultCallback !== 'function') {
throw new TypeError(EzTest._CONST.CallbackTypeErrorMsg);
}
haltWhenFailed = Boolean(haltWhenFailed || false);
if (this._running) {
throw new Error(EzTest._CONST.AlreadyRunErrorMsg);
}
this._running = true;
// Reset.
this._passCount = 0;
this._runCount = 0;
this._testIndex = -1;
setTimeout(this._runNext.bind(this, finalResultCallback, singleResultCallback, haltWhenFailed), 0);
return this._testCount;
};
/**
* Callback when all tests are run.
* @callback EzTest~TestSetResultCallback
* @param {integer} passed How many tests are passed.
* @param {integer} total How many tests are run. This will be smaller than the total test count if the test run stopped for an exception.
* @since 0.0.1
*/
/**
* Callback when a single test is run.
* @callback EzTest~SingleTestResultCallback
* @param {string} title Title of the test.
* @param {boolean} passed `true` if the test is passed.
* @param {number} duration Time in milliseconds spent in the test. `-1` when test throws.
* @param {boolean} async `true` if the test is async.
* @param {boolean} timeout `true` if the test failed due to timeout.
* @since 0.0.1
*/
EzTest.prototype._runNext = function (finalResultCallback, singleResultCallback, haltWhenFailed) {
this._testIndex++;
// Check exit condition.
if (this._testIndex >= this._tests.length) {
// All tests are run.
this._running = false;
finalResultCallback(this._passCount, this._runCount);
return;
}
// else
this._runCount++;
var testCase = this._tests[this._testIndex],
testState = {
result: null,
passed: false,
startTime: 0,
endTime: 0,
timerId: null,
timedout: false,
asyncPass: null,
asyncFail: null,
asyncActionIgnored: false,
asyncResultCallback: null
};
if (testCase.async) {
// Async test.
testState.asyncResultCallback = function (testSet, testCase, singleResultCallback, haltWhenFailed, next) {
if (this.passed === true) {
testSet._passCount++;
}
singleResultCallback(testCase.title, this.passed, this.endTime - this.startTime, true, this.timedout);
if (haltWhenFailed && this.passed === false) {
// Move pointer to the end so the tests will end.
testSet._testIndex = testSet._tests.length;
}
setTimeout(next, 0);
}.bind(testState, this, testCase, singleResultCallback, haltWhenFailed, this._runNext.bind(this, finalResultCallback, singleResultCallback, haltWhenFailed));
testState.asyncPass = function () {
var callTime = Date.now();
// Cancel timer if present.
if (this.timerId) {
clearTimeout(this.timerId);
this.timerId = null;
}
if (this.asyncActionIgnored) {
return;
}
// else
// Passed without timeout.
this.endTime = callTime;
this.passed = true;
this.asyncResultCallback();
}.bind(testState);
testState.asyncFail = function () {
var callTime = Date.now();
// Cancel timer if present.
if (this.timerId) {
clearTimeout(this.timerId);
this.timerId = null;
}
if (this.asyncActionIgnored) {
return;
}
// else
// Failed without timeout.
this.endTime = callTime;
this.passed = false;
this.asyncResultCallback();
}.bind(testState);
try {
testState.startTime = Date.now();
if (testCase.timeout > 0) {
// Start timeout.
testState.timerId = setTimeout(function (testCase) {
// Timeout.
this.endTime = Date.now();
this.asyncActionIgnored = true;
this.passed = false;
this.timedout = true;
this.timerId = null;
this.asyncResultCallback();
}.bind(testState, testCase), testCase.timeout);
}
testState.result = testCase.func(testState.asyncPass, testState.asyncFail);
} catch(e) {
if (testState.timerId) {
clearTimeout(testState.timerId);
testState.timerId = null;
}
console.log(e);
testState.asyncActionIgnored = true;
testState.endTime = testState.startTime - 1;
testState.passed = false;
testState.result = null;
testState.asyncResultCallback();
}
//!
} else {
// Sync test.
try {
testState.startTime = Date.now();
testState.result = testCase.func();
testState.endTime = Date.now();
} catch(e) {
testState.endTime = testState.startTime - 1;
console.log(e);
testState.result = null;
}
if (testState.result === true) {
testState.passed = true;
this._passCount++;
}
singleResultCallback(testCase.title, testState.passed, testState.endTime - testState.startTime, false, false);
if (haltWhenFailed && testState.passed === false) {
// Move pointer to the end so the tests will end.
this._testIndex = this._tests.length;
}
setTimeout(this._runNext.bind(this, finalResultCallback, singleResultCallback, haltWhenFailed), 0);
}
};
if (require.main !== module) {
// Loaded by another script.
module.exports = EzTest;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment