semtest
Version:
NodeJs Unit test framework combining Tape, Proxyquire and Sinon
295 lines (253 loc) • 14.3 kB
HTML
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Home</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">Home</h1>
<h3>semtest 2.0.1</h3>
<section>
<article><p><a href="https://travis-ci.org/StephaneTrebel/semtest"><img src="https://travis-ci.org/StephaneTrebel/semtest.svg?branch=master" alt="Build Status"></a>
<a href="https://coveralls.io/github/StephaneTrebel/semtest?branch=master"><img src="https://coveralls.io/repos/github/StephaneTrebel/semtest/badge.svg?branch=master" alt="Coverage Status"></a>
<a href="https://codeclimate.com/github/StephaneTrebel/semtest"><img src="https://codeclimate.com/github/StephaneTrebel/semtest/badges/gpa.svg" alt="Code Climate"></a>
<a href="https://david-dm.org/StephaneTrebel/semtest"><img src="https://david-dm.org/StephaneTrebel/semtest.svg" alt="Dependency Status"></a>
<a href="https://david-dm.org/StephaneTrebel/semtest##info=devDependencies"><img src="https://david-dm.org/StephaneTrebel/semtest/dev-status.svg" alt="devDependency Status"></a></p>
<h1>Semtest</h1><p>A(nother ?) light unit test framework for nodejs</p>
<p><em>semtest: (javascript) => TAP</em></p>
<h2>So what is this about ?</h2><p>Concieved during the time I was writing <a href="https://github.com/StephaneTrebel/semverse">Semverse</a>, Semtest is a simple yet efficient unit test framework for nodejs apps.
Its main goal (besides honing my skills on developing and maintaining an npm module) is to alleviate all the boilerplate around:</p>
<ul>
<li>testing code with Tape (in its <a href="https://github.com/spion/blue-tape">Blue-Tape</a> version)</li>
<li>mocking external dependencies with <a href="https://github.com/thlorenz/proxyquire">Proxyquire</a></li>
<li>mocking internal dependencies with <a href="http://sinonjs.org">SinonJs</a></li>
<li>enforcing unit test best practices, such as no state sharing between tests, function focused tests, and self-documenting code</li>
</ul>
<p>It is an essay on abstracting these three tools in a practical one, suited for my needs on Semverse.</p>
<p>I also decided that I would apply the same standards I use on Semverse:</p>
<ul>
<li>100 percent unit test coverage</li>
<li>JSLint compliance</li>
<li>Functional programming all the way !</li>
<li>Automatic semantic versioning</li>
</ul>
<h2>Installation</h2><p>Installation is pretty straightforward :</p>
<pre class="prettyprint source lang-bash"><code>$ npm install --save-dev semtest</code></pre><h2>Usage</h2><pre class="prettyprint source lang-bash"><code>$ node your_spec_file.js</code></pre><p>or, since tape allows for globbing:</p>
<pre class="prettyprint source lang-bash"><code>$ tape '**/*.spec.js'</code></pre><p>or, since a lot of us uses build pipelines, you can pipe the sources with your
favorite build tool. These specs files are autonomous javascript files so you
can run them anyway you want. Hurray, separation of concerns !</p>
<h2>Documentation</h2><p><strong>NOTE</strong> : What's described here is the final API Semtest will use. Until then
please refer the code to know how to use it (hint: there is no global semtest
function for the moment. This work is done by the first call of <code>prepareForTests</code>).</p>
<p>Using Semtest is a three step process:</p>
<ol>
<li><p>You create a unit test module wrapper for your project. A unit test module
wrapper is simply a function that will allow you to get stubbed modules for
unit testing. This is what the main Semtest function does:
<code>semtest: (options) => unitTestModuleWrapper</code></p>
</li>
<li><p>You instanciate your stubbed module anytime by calling the newly created
wrapper with the module name (with full path) and custom stubs related to
the current unit test, if needed:
<code>unitTestModuleWrapper: (moduleName, customStubs) => stubbedModule</code></p>
</li>
<li><p>You execute your unit tests with a clean, predefined structure that will
help you both ensure your module works as expected regardless of its
dependencies, and document your code behaviour way better than any comment
could ever do:
<code>executeTests: (moduleDescription, functionAssertions) => TAP output</code></p>
</li>
</ol>
<h3>1. Creating your unit test module wrapper:</h3><pre class="prettyprint source lang-javascript"><code>// Semtest unit test module wrapper is the function you get when requiring the
module:
const semtest = require("semtest");
/**
* Semtest main function is called with an options hash map that will allow you to:
* - optionnaly use aliases for your dependencies. Default is the classic
* name for node modules (like "fs", "lodash"), and full path + name
* for local project modules (like "/home/me/myProjects/myModule.js")
* - optionnaly define default stubs you want to use for them. Default is no
* methods are available at all for each module: every "require" will give an
* empty object
*/
const myModuleWrapper = semtest({
// For each dependency:
["lodash/fp"]: {
// Aliases are defined as key "@alias":
"@alias": "lodash",
// And default stubs as "@stubs":
"@stubs": {
// Here we want to keep lodash functions untouched to use them in our
// unit tests so instead of redefining them (remember that the
// default disallows any method call !), we just use the Proxyquire way
// of stating "just let it as-is":
"@noCallThru": false
}
},
["/home/me/myProject/myModule.js"]: {
// Aliases for local modules are very convenient because we won't have
// to specify the full path everytime
"@alias": "myModule",
"@stubs": {
// Stubs can be anything: basic types like strings, numbers:
dependencyA: "foo",
// Or, more commonly, objects:
dependencyB: {
foo : "bar",
baz : true
}
// or functions:
dependencyC: () => true,
// or, even more likely, object with methods:
dependencyD: {
methodA: (foo) => (foo.bar),
methodB: (foo) => foo.then(() => true)
}
// In this "myModule" case, we don't use @noCallThru because the usual
// best practice is to define stubs at unit test level. So @noCallThru
// is true by default.
}
}
});</code></pre><h3>2. Using the wrapper to instanciate a module</h3><p>/**</p>
<ul>
<li>The wrapper is a function that you will call with a module full</li>
<li>path, that will output a "required" version of the module where all</li>
<li>dependencies will have been stubbed, either at the local level (with custom</li>
<li>stubs you want to use for the instanciation) or at wrapper level (stubs that</li>
<li>you've defined when you created the unit test module wrapper).
*/
const myStubbedModule = myModuleWrapper(
'~/projects/myProject/myModule.js',
{<pre class="prettyprint source"><code> dependencyC: (foo) => (foo) ? "qux" : "quz"</code></pre> }
);</li>
</ul>
<p>/**</p>
<ul>
<li>"myStubbedModule" is then like "myModule", except that its "dependencyC"</li>
<li>dependency (used in "myModule" as a 'require("dependencyC")' statement) will</li>
<li>just be the <code>(foo) => ...</code> function instead of <code>() => true</code></li>
<li>(which was the stub that was specified earlier when the wrapper was created)</li>
<li>and, more important, instead of whatever it was originally, be it a node native</li>
<li>module (like "fs" or "path"), a classic node module (like "lodash") or a module</li>
<li>from the local project..
*/
```</li>
</ul>
<h3>3. Using your stubbed module to perform unit tests</h3><p>Now that you have a stubbed module, you can unit test it very easily with one
of Semtest methods: <code>executeTests</code>.</p>
<p><code>executeTests</code> is a function that takes as input the module description
(for documentation) and an
array of function assertions blocks (more on that later) and output unit tests
results in TAP format on stdout:
<code>executeTests: (moduleDescription, functionAssertionsBlocks) => TAP (on
stdout)</code></p>
<pre class="prettyprint source lang-javascript"><code>// Let's consider a "myModule" method that uses "dependencyC":
myModule.myFunction = (a) => dependencyC(a);
// Then we can use the stubbed "myModule" we have created above to unit test it
// like this:
semtest.executeTests("My powerful module", [{
name: "myFunction()",
assertions: [{
when: "it's called with a truthy value",
should: "return 'qux'",
test: function (t) {
t.equal(
myStubbedModule.myFunction(true),
"qux"
);
t.end();
}
}]
}]);
// This will output a TAP stream that you can pipe into TAP readers like faucet
// or tap-spec</code></pre><h3>4. Assertions structure</h3><p>Assertions are objects with three keys:</p>
<pre class="prettyprint source"><code> - "when" and "should" are properties that will document your unit test
- "test" is the actual unit test, written like a Blue-Tape function, which
is a function that take the Blue-Tape assert object as parameter and
should either output a promise (whose resolved/rejected status will
pass or fail the test) or call the "plan"/"end" Tape methods.</code></pre><p>Assertions for a function are bundled in a function assertion block, which is
an object with two keys:</p>
<pre class="prettyprint source"><code>- "name" is the function name
- "assertions" is an array of assertions, defined above.</code></pre><p><code>executeTests</code> will then, take an array of function assertion blocks as its
second input to, as you can expect it, run every unit test, of every function.</p>
<p>Every assertions will then be output on stdout as TAP with a description built like
this:</p>
<p><code>"module" - "function": when "assertion.when", it should "assertion.should"</code></p>
<p>If we reuse our myModule example and pipe the result in faucet, we'll get:</p>
<pre class="prettyprint source lang-bash"><code>$ node myModule.spec.js | faucet
myModule - myFunction: when it's called with a truthy value, it should return 'qux'
# tests 1
# pass 1
ok</code></pre><p>The additional bonus of such structure is that you can fold your code in spec
files to only show the documentation part, enabling your to truly document your
code with specs, which they are for in the first place :</p>
<pre class="prettyprint source lang-javascript"><code>executeTests("Test helpers library", [{
name: "rejectFnHO()",
assertions: [{
when: "its returned function is not given a rejection reason",
should: "return a function that returns the base reason",
test: (test) => test((t) =>
[..............................................]
)
}, {
when: "its returned function is given a rejection reason",
should: "return a function that returns the given reason",
test: (test) => test((t) =>
[..............................................]
)
}]
}</code></pre><p>Here, whatever the <code>[.....]</code> cover, we can focus on the litteral description of
our code instead.</p>
<p>Since the spec files aren't meant to be delivered in production, we can make
them as bloated as we want, for the sake of code literacy instead of
performance.</p>
<h2>Coverage</h2><p>This module is fully compatible with any coverage tool as far as I know. I recommend
<a href="http://github.com/bcoe/nyc">nyc</a> but you're free to use your favorite.</p>
<h2>Utility functions</h2><p><code>utilityFunctions</code> is a collection of one-liners that are very useful to
stub you dependencies with:</p>
<pre class="prettyprint source lang-javascript"><code>// Instead of defining the same stubs over and over again :
const myModule = myModuleWrapper(
'~/projects/myProject/myModule.js',
{
foo: () => Promise.resolve(true),
bar: () => Promise.reject(new Error("Oups")),
baz: () => () => Promise.reject(new Error("Oups")),
qux: () => () => null,
derp: (a) => a
}
);
// We can use utilityFunctions as quicker and more consistent way :
const u = require("semtest").utilityFunctions;
const myModule = myModuleWrapper(
'~/projects/myProject/myModule.js',
{
foo: u.resolveFn(true),
bar: u.rejectFn("Oups"),
baz: u.rejectFnHO("Oups"),
qux: u.nullFn,
derp: u.idFn
}
);</code></pre><h2>Licence</h2><p>Do whatever you want with the code, just tell me about it so that I can know at
least one person read it xD</p>
<h2>Contributing</h2><p>Open issues and send me PRs like there's no tomorrow, be my guest !</p></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>