testdouble
Version:
A minimal test double library for TDD with JavaScript
118 lines (117 loc) • 4.41 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
const lodash_1 = require("./wrap/lodash");
const proxy_safe_clone_deep_with_1 = require("./wrap/proxy-safe-clone-deep-with");
const calls_1 = require("./store/calls");
const store_1 = require("./store");
const arguments_1 = require("./stringify/arguments");
const stubbings_1 = require("./store/stubbings");
const symbols_1 = require("./symbols");
function explain(testDouble) {
if (lodash_1.default.isFunction(testDouble)) {
return explainFunction(testDouble);
}
else if (lodash_1.default.isObject(testDouble)) {
return explainObject(testDouble);
}
else {
return explainNonTestDouble(testDouble);
}
}
exports.default = explain;
function explainObject(obj) {
const { explanations, children } = explainChildren(obj);
return {
name: null,
callCount: 0,
calls: [],
description: describeObject(explanations),
children,
isTestDouble: explanations.length > 0
};
}
function explainChildren(thing) {
const explanations = [];
const children = (0, proxy_safe_clone_deep_with_1.default)(thing, (val, key, obj, stack) => {
if (lodash_1.default.isFunction(val) && stack) {
return lodash_1.default.tap(explainFunction(val), (explanation) => {
if (explanation.isTestDouble)
explanations.push(explanation);
});
}
});
return { explanations, children };
}
function describeObject(explanations) {
const count = explanations.length;
if (count === 0)
return 'This object contains no test doubles';
return `This object contains ${count} test double function${count > 1 ? 's' : ''}: [${lodash_1.default.map(explanations, e => `"${e.name}"`).join(', ')}]`;
}
function explainFunction(testDouble) {
if (store_1.default.for(testDouble, false) == null) {
return explainNonTestDouble(testDouble);
}
const calls = calls_1.default.for(testDouble);
const stubs = stubbings_1.default.for(testDouble);
const { children } = explainChildren(testDouble);
return {
name: store_1.default.for(testDouble).name,
callCount: calls.length,
calls,
description: testdoubleDescription(testDouble, stubs, calls) +
stubbingDescription(stubs) +
callDescription(calls),
children,
isTestDouble: true
};
}
function explainNonTestDouble(thing) {
return ({
name: undefined,
callCount: 0,
calls: [],
description: `This is not a test double${lodash_1.default.isFunction(thing) ? ' function' : ''}.`,
isTestDouble: false
});
}
function testdoubleDescription(testDouble, stubs, calls) {
return `This test double ${stringifyName(testDouble)}has ${stubs.length} stubbings and ${calls.length} invocations.`;
}
function stubbingDescription(stubs) {
return stubs.length > 0
? lodash_1.default.reduce(stubs, (desc, stub) => desc + `\n - when called with \`(${(0, arguments_1.default)(stub.args)})\`, then ${planFor(stub)} ${argsFor(stub)}.`, '\n\nStubbings:')
: '';
}
function planFor(stub) {
switch (stub.config.plan) {
case 'thenCallback': return 'callback';
case 'thenResolve': return 'resolve';
case 'thenReject': return 'reject';
default: return 'return';
}
}
function argsFor(stub) {
switch (stub.config.plan) {
case 'thenCallback': return `\`(${(0, arguments_1.default)(stub.stubbedValues, ', ')})\``;
default: return (0, arguments_1.default)(stub.stubbedValues, ', then ', '`');
}
}
function callDescription(calls) {
return calls.length > 0
? lodash_1.default.reduce(calls, (desc, call) => {
let argDescription;
if (call.cloneArgs !== symbols_1.default.uncloneable) {
argDescription = `\`(${(0, arguments_1.default)(call.cloneArgs)})\`.`;
}
else {
argDescription = `\`(${(0, arguments_1.default)(call.args)})\` [Cloning argument values failed; displaying current references]`;
}
return desc + `\n - called with ${argDescription}`;
}, '\n\nInvocations:')
: '';
}
function stringifyName(testDouble) {
const name = store_1.default.for(testDouble).name;
return name ? `\`${name}\` ` : '';
}
;