Quibble is a terser (and more magical) alternative to packages like
proxyquire,
sandboxed-module and
mockery for mocking out dependencies
in tests of Node.js modules. Using quibble
you can replace
how require()
will behave for a given path. Its intended use is squarely
focused on unit testing. It is almost-but-not-quite a private dependency of
testdouble.js, as it
implements the td.replace()
function's module-replacement behavior.
Say we're testing pants:
quibble = require('quibble')
describe('pants', function(){
var subject, legs;
beforeEach(function(){
legs = quibble('./../lib/legs', function(){ return 'a leg';});
subject = require('./../lib/pants');
});
it('contains legs', function() {
expect(subject().left).toContain('a leg')
expect(subject().right).toContain('a leg')
})
});
That way, when the subject
loaded from lib/pants
runs require('./legs')
,
it will get back the function that returns 'a leg'
. The fake value is also
returned by quibble
, which makes it easy to set and assign a test double in a
one-liner.
For more info on how this module is really intended to be used, check out its inclusion in testdouble.js
There's only one option: what you want to do with quibbled modules by default.
Say you're pulling in testdouble.js and you want every quibbled module to default to a single test double function with a name that matches its absolute path. You could do this:
quibble = require('quibble')
beforeEach(function(){
quibble.config({
defaultFakeCreator: function(path) {
return require('testdouble').create(path);
}
});
});
With this set up, running quibble('./some/path')
will default to replacing all
require('../anything/that/matches/some/path')
invocations with a test double named
after the absolute path resolved to by './some/path'
.
Spiffy!
Note:
defaultFakeCreator
is not supported for ES Module stubbing
Quibble supports ES Modules. Quibble implements ES module support using ES Module Loaders which are the official way to "patch" Node.js' module loading mechanism for ESM.
Note that Loader support is currently experimental and unstable. We are doing our best to track the changes in the specification for the upcoming Node.js versions.
If you're running a Node.js version smaller than v20.6.0, you must run Node with the quibble
package as a loader:
node --loader=quibble ...
Most test runners allow you to specify this in their command line, e.g. for Mocha:
mocha --loader=quibble ...
The quibble
loader will enable the replacement of the ES modules with the stubs you specify, and
without it, the stubbing will be ignored.
For versions larger or equal to v20.6.0, there is no need to specify a --loader
, as registering the loader
happens automatically once you use the API.
defaultFakeCreator
is not yet supported.quibble
ESM APIThe API is similar to the CommonJS API, but uses quibble.esm
function, and is async:
// a-module.mjs (ESM)
export const life = 42;
export default 'universe';
// uses-a-module.mjs
import universe, {life} from './a-module.mjs';
console.log(life, universe);
(async function () {
await quibble.esm('./a-module.mjs', {life: 41}, 'replacement universe');
await import('./uses-some-module.mjs');
// ==> logs: 41, replacement universe
})();
The parameters to be given to quibble.esm
for ESM modules are:
default
export you can either define a default
property here or use the third argument, but not both at same time.default
key, you can define the default stub with this argument.Note that quibble.reset
works the same as for CommonJS modules
ESM support also exposes the function quibble.esmImportWithPath
which both imports a module and
resolves the path to the module that is the package's entry point:
async quibble.esmImportWithPath(importPath)
: imports a module, just like import(importPath)
,
but returns an object with two properties:
module
: the module returned by await import(importPath)
.modulePath
: the full path to the module (file) that is the entry point to the package/module.Note that when mocking internal Node.js modules (e.g. "fs")), you need to mock the named exports both as named exports and as properties in the default export, because Node.js exports internal modules both as named exports and as a default object. Example:
const fsExports = {
readFileSync: function (path) {
console.log("using quibbled readFileSyns... yay!");
return "Looks like 'fs' was replaced correctly.";
},
}
await quibble.esm("fs", fsExports, fsExports);
A few things that stand out about quibble:
require
just as you normally would). The instantiation
style of other libs is a little different (e.g. require('./my/subject', {'/this/thing': stub})
reset()
method that undoes everything, intended to be run afterEach
test runs