UNPKG

3.9 kBJavaScriptView Raw
1import _ from './wrap/lodash'
2import argsMatch from './args-match'
3import callsStore from './store/calls'
4import log from './log'
5import store from './store'
6import stringifyArgs from './stringify/arguments'
7import stubbingsStore from './store/stubbings'
8import notifyAfterSatisfaction from './matchers/notify-after-satisfaction'
9import cloneDeepIfPossible from './clone-deep-if-possible'
10import symbols from './symbols'
11
12export default (__userDoesRehearsalInvocationHere__, config = {}) => {
13 const last = callsStore.pop()
14 ensureRehearsalOccurred(last)
15 ensureCloneableIfCloneArgs(last, config)
16 if (callsStore.wasInvoked(last.testDouble, last.args, config)) {
17 notifyMatchers(last.testDouble, last.args, config)
18 warnIfStubbed(last.testDouble, last.args)
19 } else {
20 log.fail(unsatisfiedErrorMessage(last.testDouble, last.args, config))
21 }
22}
23
24const ensureRehearsalOccurred = (last) => {
25 if (!last) {
26 log.error('td.verify', `\
27No test double invocation detected for \`verify()\`.
28
29 Usage:
30 verify(myTestDouble('foo'))\
31`)
32 }
33}
34
35function ensureCloneableIfCloneArgs (last, config) {
36 if (config.cloneArgs && cloneDeepIfPossible(last.args) === symbols.uncloneable) {
37 return log.error('td.verify', `\
38Failed to deep-clone arguments. Ensure lodash _.cloneDeep works on them
39`)
40 }
41}
42
43const notifyMatchers = (testDouble, expectedArgs, config) => {
44 _.each(callsStore.where(testDouble, expectedArgs, config), (invocation) => {
45 notifyAfterSatisfaction(expectedArgs, invocation.args)
46 })
47}
48
49const warnIfStubbed = (testDouble, actualArgs) => {
50 if (_.some(stubbingsStore.for(testDouble), (stubbing) =>
51 argsMatch(stubbing.args, actualArgs, stubbing.config))
52 ) {
53 log.warn('td.verify',
54 `test double${stringifyName(testDouble)} was both stubbed and verified with arguments (${stringifyArgs(actualArgs)}), which is redundant and probably unnecessary.`,
55 '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'
56 )
57 }
58}
59
60const unsatisfiedErrorMessage = (testDouble, args, config) =>
61 baseSummary(testDouble, args, config) +
62 matchedInvocationSummary(testDouble, args, config) +
63 invocationSummary(testDouble, args, config)
64
65const stringifyName = (testDouble) => {
66 const name = store.for(testDouble).name
67 return name ? ` \`${name}\`` : ''
68}
69
70const baseSummary = (testDouble, args, config) =>
71 `\
72Unsatisfied verification on test double${stringifyName(testDouble)}.
73
74 Wanted:
75 - called with \`(${stringifyArgs(args)})\`${timesMessage(config)}${ignoreMessage(config)}.\
76`
77
78const invocationSummary = (testDouble, args, config) => {
79 const calls = callsStore.for(testDouble)
80 if (calls.length === 0) {
81 return '\n\n But there were no invocations of the test double.'
82 } else {
83 return _.reduce(calls, (desc, call) =>
84 desc + `\n - called with \`(${stringifyArgs(call.args)})\`.`
85 , '\n\n All calls of the test double, in order were:')
86 }
87}
88
89const matchedInvocationSummary = (testDouble, args, config) => {
90 const calls = callsStore.where(testDouble, args, config)
91 const expectedCalls = config.times || 0
92
93 if (calls.length === 0 || calls.length > expectedCalls) {
94 return ''
95 } else {
96 return _.reduce(_.groupBy(calls, 'args'), (desc, callsMatchingArgs, args) =>
97 desc + `\n - called ${pluralize(callsMatchingArgs.length, 'time')} with \`(${stringifyArgs(callsMatchingArgs[0].args)})\`.`
98 , `\n\n ${pluralize(calls.length, 'call')} that satisfied this verification:`)
99 }
100}
101
102const pluralize = (x, msg) =>
103 `${x} ${msg}${x === 1 ? '' : 's'}`
104
105const timesMessage = (config) =>
106 config.times != null
107 ? ` ${pluralize(config.times, 'time')}`
108 : ''
109
110const ignoreMessage = (config) =>
111 config.ignoreExtraArgs != null
112 ? ', ignoring any additional arguments'
113 : ''