UNPKG

testdouble

Version:

A minimal test double library for TDD with JavaScript

114 lines (97 loc) • 3.9 kB
import _ from './wrap/lodash' import argsMatch from './args-match' import callsStore from './store/calls' import log from './log' import store from './store' import stringifyArgs from './stringify/arguments' import stubbingsStore from './store/stubbings' import notifyAfterSatisfaction from './matchers/notify-after-satisfaction' import cloneDeepIfPossible from './clone-deep-if-possible' import symbols from './symbols' export default (__userDoesRehearsalInvocationHere__, config = {}) => { const last = callsStore.pop() ensureRehearsalOccurred(last) ensureCloneableIfCloneArgs(last, config) if (callsStore.wasInvoked(last.testDouble, last.args, config)) { notifyMatchers(last.testDouble, last.args, config) warnIfStubbed(last.testDouble, last.args) } else { log.fail(unsatisfiedErrorMessage(last.testDouble, last.args, config)) } } const ensureRehearsalOccurred = (last) => { if (!last) { log.error('td.verify', `\ No test double invocation detected for \`verify()\`. Usage: verify(myTestDouble('foo'))\ `) } } function ensureCloneableIfCloneArgs (last, config) { if (config.cloneArgs && cloneDeepIfPossible(last.args) === symbols.uncloneable) { return log.error('td.verify', `\ Failed to deep-clone arguments. Ensure lodash _.cloneDeep works on them `) } } const notifyMatchers = (testDouble, expectedArgs, config) => { _.each(callsStore.where(testDouble, expectedArgs, config), (invocation) => { notifyAfterSatisfaction(expectedArgs, invocation.args) }) } const warnIfStubbed = (testDouble, actualArgs) => { if (_.some(stubbingsStore.for(testDouble), (stubbing) => argsMatch(stubbing.args, actualArgs, stubbing.config)) ) { log.warn('td.verify', `test double${stringifyName(testDouble)} was both stubbed and verified with arguments (${stringifyArgs(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.for(testDouble).name return name ? ` \`${name}\`` : '' } const baseSummary = (testDouble, args, config) => `\ Unsatisfied verification on test double${stringifyName(testDouble)}. Wanted: - called with \`(${stringifyArgs(args)})\`${timesMessage(config)}${ignoreMessage(config)}.\ ` const invocationSummary = (testDouble, args, config) => { const calls = callsStore.for(testDouble) if (calls.length === 0) { return '\n\n But there were no invocations of the test double.' } else { return _.reduce(calls, (desc, call) => desc + `\n - called with \`(${stringifyArgs(call.args)})\`.` , '\n\n All calls of the test double, in order were:') } } const matchedInvocationSummary = (testDouble, args, config) => { const calls = callsStore.where(testDouble, args, config) const expectedCalls = config.times || 0 if (calls.length === 0 || calls.length > expectedCalls) { return '' } else { return _.reduce(_.groupBy(calls, 'args'), (desc, callsMatchingArgs, args) => desc + `\n - called ${pluralize(callsMatchingArgs.length, 'time')} with \`(${stringifyArgs(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' : ''