UNPKG

semtest

Version:

NodeJs Unit test framework combining Tape, Proxyquire and Sinon

277 lines (247 loc) 8.93 kB
<!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&lt;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&lt;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&lt;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>