testdouble
Version:
A minimal test double library for TDD with JavaScript
91 lines (88 loc) • 4.39 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
const lodash_1 = require("./wrap/lodash");
const args_match_1 = require("./args-match");
const calls_1 = require("./store/calls");
const log_1 = require("./log");
const store_1 = require("./store");
const arguments_1 = require("./stringify/arguments");
const stubbings_1 = require("./store/stubbings");
const notify_after_satisfaction_1 = require("./matchers/notify-after-satisfaction");
const clone_deep_if_possible_1 = require("./clone-deep-if-possible");
const symbols_1 = require("./symbols");
exports.default = (__userDoesRehearsalInvocationHere__, config = {}) => {
const last = calls_1.default.pop();
ensureRehearsalOccurred(last);
ensureCloneableIfCloneArgs(last, config);
if (calls_1.default.wasInvoked(last.testDouble, last.args, config)) {
notifyMatchers(last.testDouble, last.args, config);
warnIfStubbed(last.testDouble, last.args);
}
else {
log_1.default.fail(unsatisfiedErrorMessage(last.testDouble, last.args, config));
}
};
const ensureRehearsalOccurred = (last) => {
if (!last) {
log_1.default.error('td.verify', `\
No test double invocation detected for \`verify()\`.
Usage:
verify(myTestDouble('foo'))\
`);
}
};
function ensureCloneableIfCloneArgs(last, config) {
if (config.cloneArgs && (0, clone_deep_if_possible_1.default)(last.args) === symbols_1.default.uncloneable) {
return log_1.default.error('td.verify', `\
Failed to deep-clone arguments. Ensure lodash _.cloneDeep works on them
`);
}
}
const notifyMatchers = (testDouble, expectedArgs, config) => {
lodash_1.default.each(calls_1.default.where(testDouble, expectedArgs, config), (invocation) => {
(0, notify_after_satisfaction_1.default)(expectedArgs, invocation.args);
});
};
const warnIfStubbed = (testDouble, actualArgs) => {
if (lodash_1.default.some(stubbings_1.default.for(testDouble), (stubbing) => (0, args_match_1.default)(stubbing.args, actualArgs, stubbing.config))) {
log_1.default.warn('td.verify', `test double${stringifyName(testDouble)} was both stubbed and verified with arguments (${(0, arguments_1.default)(actualArgs)}), which is redundant and probably unnecessary.`, 'https://github.com/testdouble/testdouble.js/blob/main/docs/B-frequently-asked-questions.md#why-shouldnt-i-call-both-tdwhen-and-tdverify-for-a-single-interaction-with-a-test-double');
}
};
const unsatisfiedErrorMessage = (testDouble, args, config) => baseSummary(testDouble, args, config) +
matchedInvocationSummary(testDouble, args, config) +
invocationSummary(testDouble, args, config);
const stringifyName = (testDouble) => {
const name = store_1.default.for(testDouble).name;
return name ? ` \`${name}\`` : '';
};
const baseSummary = (testDouble, args, config) => `\
Unsatisfied verification on test double${stringifyName(testDouble)}.
Wanted:
- called with \`(${(0, arguments_1.default)(args)})\`${timesMessage(config)}${ignoreMessage(config)}.\
`;
const invocationSummary = (testDouble, args, config) => {
const calls = calls_1.default.for(testDouble);
if (calls.length === 0) {
return '\n\n But there were no invocations of the test double.';
}
else {
return lodash_1.default.reduce(calls, (desc, call) => desc + `\n - called with \`(${(0, arguments_1.default)(call.args)})\`.`, '\n\n All calls of the test double, in order were:');
}
};
const matchedInvocationSummary = (testDouble, args, config) => {
const calls = calls_1.default.where(testDouble, args, config);
const expectedCalls = config.times || 0;
if (calls.length === 0 || calls.length > expectedCalls) {
return '';
}
else {
return lodash_1.default.reduce(lodash_1.default.groupBy(calls, 'args'), (desc, callsMatchingArgs, args) => desc + `\n - called ${pluralize(callsMatchingArgs.length, 'time')} with \`(${(0, arguments_1.default)(callsMatchingArgs[0].args)})\`.`, `\n\n ${pluralize(calls.length, 'call')} that satisfied this verification:`);
}
};
const pluralize = (x, msg) => `${x} ${msg}${x === 1 ? '' : 's'}`;
const timesMessage = (config) => config.times != null
? ` ${pluralize(config.times, 'time')}`
: '';
const ignoreMessage = (config) => config.ignoreExtraArgs != null
? ', ignoring any additional arguments'
: '';
;