1 | import _ from './wrap/lodash'
|
2 | import argsMatch from './args-match'
|
3 | import callsStore from './store/calls'
|
4 | import log from './log'
|
5 | import store from './store'
|
6 | import stringifyArgs from './stringify/arguments'
|
7 | import stubbingsStore from './store/stubbings'
|
8 | import notifyAfterSatisfaction from './matchers/notify-after-satisfaction'
|
9 | import cloneDeepIfPossible from './clone-deep-if-possible'
|
10 | import symbols from './symbols'
|
11 |
|
12 | export 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 |
|
24 | const ensureRehearsalOccurred = (last) => {
|
25 | if (!last) {
|
26 | log.error('td.verify', `\
|
27 | No test double invocation detected for \`verify()\`.
|
28 |
|
29 | Usage:
|
30 | verify(myTestDouble('foo'))\
|
31 | `)
|
32 | }
|
33 | }
|
34 |
|
35 | function ensureCloneableIfCloneArgs (last, config) {
|
36 | if (config.cloneArgs && cloneDeepIfPossible(last.args) === symbols.uncloneable) {
|
37 | return log.error('td.verify', `\
|
38 | Failed to deep-clone arguments. Ensure lodash _.cloneDeep works on them
|
39 | `)
|
40 | }
|
41 | }
|
42 |
|
43 | const notifyMatchers = (testDouble, expectedArgs, config) => {
|
44 | _.each(callsStore.where(testDouble, expectedArgs, config), (invocation) => {
|
45 | notifyAfterSatisfaction(expectedArgs, invocation.args)
|
46 | })
|
47 | }
|
48 |
|
49 | const 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 |
|
60 | const unsatisfiedErrorMessage = (testDouble, args, config) =>
|
61 | baseSummary(testDouble, args, config) +
|
62 | matchedInvocationSummary(testDouble, args, config) +
|
63 | invocationSummary(testDouble, args, config)
|
64 |
|
65 | const stringifyName = (testDouble) => {
|
66 | const name = store.for(testDouble).name
|
67 | return name ? ` \`${name}\`` : ''
|
68 | }
|
69 |
|
70 | const baseSummary = (testDouble, args, config) =>
|
71 | `\
|
72 | Unsatisfied verification on test double${stringifyName(testDouble)}.
|
73 |
|
74 | Wanted:
|
75 | - called with \`(${stringifyArgs(args)})\`${timesMessage(config)}${ignoreMessage(config)}.\
|
76 | `
|
77 |
|
78 | const 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 |
|
89 | const 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 |
|
102 | const pluralize = (x, msg) =>
|
103 | `${x} ${msg}${x === 1 ? '' : 's'}`
|
104 |
|
105 | const timesMessage = (config) =>
|
106 | config.times != null
|
107 | ? ` ${pluralize(config.times, 'time')}`
|
108 | : ''
|
109 |
|
110 | const ignoreMessage = (config) =>
|
111 | config.ignoreExtraArgs != null
|
112 | ? ', ignoring any additional arguments'
|
113 | : ''
|