semtest
Version:
NodeJs Unit test framework combining Tape, Proxyquire and Sinon
277 lines (247 loc) • 8.93 kB
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: main.js</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Source: main.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>/**
* ### Test helpers
*
* This module hosts everything needed for tests.
* Abstraction over the expection engine, abstraction over the basic functions,
* this module is the Test toolbelt.
* Little to no test framework logic should be in unit tests to keep them
* focused on code documentation.
*
* @module Library/Test-Helpers
*/
"use strict";
const lodash = require("lodash/fp");
const {
defaultsDeep,
has,
flow,
keys,
map
} = lodash;
const BPromise = require("bluebird");
const tape = require("blue-tape");
const sinon = require("sinon");
const proxyquire = require("proxyquire").noPreserveCache().noCallThru();
/**
* Return a function that will execute a test with a given description
* @param {Function} testFn - Test engine
* @param {String} testDescription - Unit test description
* @param {Function} cb - Callback called with test engine object
* @return {Function} Unit test handler
*/
exports.unitTest = (testFn, testDescription) => (cb) =>
testFn(`${testDescription}`, cb);
/**
* Returns a function that will perform several tests, each with a concatenated
* description.
* Descriptions will be concatenated to form a test statement that will have this
* structure: "Module - Function(): When XXXXXXXXX, it should YYYYYYYYY
* @curried
* @param {String} mode - Execution mode(null = normal, skip, or only)
* @param {String} moduleName - Tested module description
* @param {Array<Object>} assertions - Assertion array to perform
* @return {Array} Assertions execution results
*/
exports.executeTestsInternal = (mode) => (moduleName, functionBlocks) =>
map((fnObj) =>
map(function(assert) {
const description = `${moduleName} - ${fnObj.name}: when ${assert.when}, it should ${assert.should}`;
const newTest = exports.unitTest(tape, description);
newTest.skip = exports.unitTest(tape.skip, description);
newTest.only = exports.unitTest(tape.only, description);
// istanbul ignore if
if (mode === "skip" || fnObj.skip) {
return assert.test(newTest.skip);
}
return assert.test(newTest);
}, fnObj.assertions), functionBlocks);
exports.executeTests = exports.executeTestsInternal();
exports.executeTests.skip = exports.executeTestsInternal("skip");
const utilityFunctions = {};
/**
* Identity function
* @param {Mixed} input - Value to be returned
* @return {Mixed} Same as input
*/
utilityFunctions.idFn = (input) => input;
/**
* Return the identity function
* @return {Function} Identity function
*/
utilityFunctions.idFnHO = () => utilityFunctions.idFn;
/**
* Returns null
* @return {Null} Null
*/
utilityFunctions.nullFn = () => null;
/**
* Returns a function that returns null
* @return {Function} Function that returns null
*/
utilityFunctions.nullFnHO = () => utilityFunctions.nullFn;
/**
* Throws an Error
* @param {String} message - Error message
* @return {Error} Error thrown
*/
utilityFunctions.throwFn = function(message) {
throw new Error(message);
};
/**
* Returns a function that throws an Error
* @param {String} message - Error message
* @return {Function} Function that throws an error
*/
utilityFunctions.throwFnHO = (message) => () => utilityFunctions.throwFn(message);
/**
* Returns a resolved Promise
* @param {Mixed} value - Resolved value
* @return {Promise<Mixed>} Promise resolved with given value
*/
utilityFunctions.resolveFn = (value) => BPromise.resolve(value);
/**
* Returns a function that return a resolved Promise with given value
* @param {Mixed} value - Resolved value
* @return {Function} Function that returns a resolved Promise with given value
*/
utilityFunctions.resolveFnHO = (value) => () => utilityFunctions.resolveFn(value);
/**
* Returns a rejected Promise
* @param {String} reason - Rejection reason
* @return {Promise<String>} Promise rejected with given reason
*/
utilityFunctions.rejectFn = (reason) => BPromise.reject(new Error(reason));
/*
* Returns a function that return a rejected Promise with given reason
* @param {String} reason - Rejection reason
* @return {Function} Function that return a rejected Promise with given reason
*/
utilityFunctions.rejectFnHO = (reason) => (otherReason) => (otherReason) ?
utilityFunctions.rejectFn(otherReason) :
utilityFunctions.rejectFn(reason);
// Abstracts Sinon.js in tests
utilityFunctions.stub = sinon.stub;
utilityFunctions.spy = sinon.spy;
/**
* Create an Express-like response mock
* @return {Object} Response mock
*/
utilityFunctions.createResponseMock = function() {
const response = {};
response.status = function(status) {
response.status = status;
return response;
};
response.json = function(body) {
response.body = body;
return response;
};
return response;
};
exports.utilityFunctions = utilityFunctions;
// Proxyquire configuration object for stubs (to prevent all calls to the
// original module)
//const noCallThru = {
//"@noCallThru": true
//};
/**
* Convert an object into another object with computed properties by using the given
* object properties names as keys declared in the given dependency
* dictionnary.
* If they are not defined, this function will assume that they refer to node
* modules instead of project-related ones during the computed property resolution.
* Usage of the dictionnary is optional, but helpful since you can then use
* aliases for modules names in your stubs declaration, instead of having to
* resolve all modules path names (since proxyquire need them)
* @example:
* Given { "foo": "bar" } as the dictionnary
* and { "fs": ..., "foo": ..., "baz": ... } as the object,
* result will be { "fs": ..., "bar": ..., "baz": ... }
* @param {Object} dictionnary - Dictionnary
* @param {Object} object - Input object
* @return {Object} Converted object
*/
exports.convertIntoComputedProperties = function(dictionnary, object) {
const newObj = {};
const objKeys = keys(object);
map(function(property) {
if (has(property, dictionnary)) {
newObj[dictionnary[property]] = object[property];
} else {
newObj[property] = object[property];
}
},
objKeys
);
return newObj;
};
/**
* Require a module with stubbed dependencies
* @param {Object} dictionnary - Dictionnary
* @param {Object} defaultStubs - Default stubs (internal dependencies) of the
* tested module
* @param {String} moduleName - Module name to proxiquire
* @param {Object} customStubs - Custom stubs for the proxified module
* @return {Mixed} Proxified module
*/
exports.requireWithStubs = function(dictionnary, defaultStubs, moduleName, customStubs) {
const stubs = flow(
defaultsDeep(exports.convertIntoComputedProperties(dictionnary, customStubs)),
defaultsDeep(exports.convertIntoComputedProperties(dictionnary, defaultStubs))
)({});
return proxyquire(
moduleName,
stubs
);
};
/**
* Prepare a module for tests by requiring it with stubs
* Module name is based on the given test file name that
* will consume the prepared module.
* @param {Object} dictionnary - Dictionnary
* @param {Object} defaultStubs - Default stubs (internal dependencies) of the
* tested module
* @param {String} testFileName - Test file name
* @param {Object} customStubs - Custom stubs for the proxified module
* @return {Mixed} Proxified module
*/
exports.prepareForTests = (dictionnary, defaultStubs) => (testFileName) => (customStubs) =>
exports.requireWithStubs(
dictionnary,
defaultStubs,
testFileName.replace(".spec", ""),
customStubs
);
</code></pre>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-Library_Test-Helpers.html">Library/Test-Helpers</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.4.1</a> on Tue Sep 27 2016 08:05:15 GMT+0200 (CEST)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>